"""
    energy_balance(object::T,meteo::Atmosphere,constants = Constants())
    energy_balance(object::Array{AbstractComponentModel},meteo::Atmosphere,constants = Constants())
    energy_balance!(object::AbstractComponentModel,meteo::Atmosphere,constants = Constants())

Computes the energy balance of a component based on the type of the model it was parameterized
with in `object.energy`.

At the moment, two models are implemented in the package:

- [`Monteith`](@ref): the model found in Monteith and Unsworth (2013)
- `Missing`: if no computation of the energy balance is needed

# Arguments

- `object::Union{AbstractComponentModel,Array{AbstractComponentModel},
Dict{String,AbstractComponentModel}}`: a [`Component`](@ref) struct, or a Dict/Array of.
- `meteo::Union{Atmosphere,Weather}`: meteorology structure, see [`Atmosphere`](@ref) or
[`Weather`](@ref)
- `constants = Constants()`: physical constants. See [`Constants`](@ref) for more details

# Note

Some models need input values for some variables. For example [`Monteith`](@ref) requires a
value for `Rₛ`, `d` and `skyFraction`. If you read the models from a file, you can
use [`init_status!`](@ref) (see examples).

# Examples

```julia
meteo = Atmosphere(T = 20.0, Wind = 1.0, P = 101.3, Rh = 0.65)

# Using the model of Monteith and Unsworth (2013) for energy, Farquhar et al. (1980) for
# photosynthesis, and Medlyn et al. (2011) for stomatal conductance:
leaf = LeafModels(energy = Monteith(),
            photosynthesis = Fvcb(),
            stomatal_conductance = Medlyn(0.03, 12.0),
            Rₛ = 13.747, skyFraction = 1.0, PPFD = 1500.0, d = 0.03)

energy_balance(leaf,meteo)

# Using a model file:
model = read_model("a-model-file.yml")

# An example model file is available here:
# "https://raw.githubusercontent.com/VEZY/PlantBiophysics/main/test/inputs/models/plant_coffee.yml"

# Initialising the mandatory variables:
init_status!(model, Rₛ = 13.747, skyFraction = 1.0, PPFD = 1500.0, Tₗ = 25.0, d = 0.03)

# NB: To know which variables has to be initialised according to the models used, you can use
# `to_initialise(leaf)`

# Running a simulation for all component types in the same scene:
energy_balance!(model, meteo)

model["Leaf"].status.Rn
model["Leaf"].status.A
model["Leaf"].status.Cᵢ
```

# References

Duursma, R. A., et B. E. Medlyn. 2012. « MAESPA: a model to study interactions between water
limitation, environmental drivers and vegetation function at tree and stand levels, with an
example application to [CO2] × drought interactions ». Geoscientific Model Development 5 (4):
919‑40. https://doi.org/10.5194/gmd-5-919-2012.

Monteith, John L., et Mike H. Unsworth. 2013. « Chapter 13 - Steady-State Heat Balance: (i)
Water Surfaces, Soil, and Vegetation ». In Principles of Environmental Physics (Fourth Edition),
edited by John L. Monteith et Mike H. Unsworth, 217‑47. Boston: Academic Press.

Schymanski, Stanislaus J., et Dani Or. 2017. « Leaf-Scale Experiments Reveal an Important
Omission in the Penman–Monteith Equation ». Hydrology and Earth System Sciences 21 (2): 685‑706.
https://doi.org/10.5194/hess-21-685-2017.

Vezy, Rémi, Mathias Christina, Olivier Roupsard, Yann Nouvellon, Remko Duursma, Belinda Medlyn,
Maxime Soma, et al. 2018. « Measuring and modelling energy partitioning in canopies of varying
complexity using MAESPA model ». Agricultural and Forest Meteorology 253‑254 (printemps): 203‑17.
https://doi.org/10.1016/j.agrformet.2018.02.005.
"""
function energy_balance!(object::AbstractComponentModel, meteo::Atmosphere, constants = Constants())
    is_init = is_initialised(object)
    !is_init && error("Some variables must be initialized before simulation")

    net_radiation!(object, meteo, constants)
    return nothing
end

# Same as above but non-mutating
function energy_balance(object::AbstractComponentModel, meteo::Atmosphere, constants = Constants())
    object_tmp = copy(object)
    energy_balance!(object_tmp, meteo, constants)
    return object_tmp.status
end

# energy_balance over several objects (e.g. all leaves of a plant) in an Array
function energy_balance!(object::O, meteo::Atmosphere, constants = Constants()) where O <: AbstractArray{<:AbstractComponentModel}

    for i in values(object)
        energy_balance!(i, meteo, constants)
    end

    return nothing
end

# same as the above but non-mutating
function energy_balance(
    object::O,
    meteo::M,
    constants = Constants()
    ) where {O <: Union{AbstractArray{<:AbstractComponentModel},AbstractDict{N,<:AbstractComponentModel} where N},M <: Union{Atmosphere,Weather}}

    # Copy the objects only once before the computation for performance reasons:
    object_tmp = copy(object)

    # Computation:
    energy_balance!(object_tmp, meteo, constants)

    # --- Extracting the outputs: ---

    # Pre-allocating the outputs:
    output = [i.status for i in object_tmp]

    for (i, obj) in enumerate(object_tmp)
        output[i] = obj.status
    end

    output = DataFrame([NamedTuple(i) for i in output])

    return output
end

# energy_balance over several objects (e.g. all leaves of a plant) in a kind of Dict.
function energy_balance!(object::O, meteo::Atmosphere, constants = Constants()) where O <: AbstractDict{N,<:AbstractComponentModel} where N

    for (k, v) in object
        energy_balance!(v, meteo, constants)
    end

    return nothing
end

# same as the above but non-mutating. In this case we add a column with the component name 😃
function energy_balance(
    object::O,
    meteo::M,
    constants = Constants()
    ) where {O <: Union{AbstractArray{<:AbstractComponentModel},AbstractDict{N,<:AbstractComponentModel} where N},M <: Union{Atmosphere,Weather}}

    # Copy the objects only once before the computation for performance reasons:
    object_tmp = copy(object)

    # Computation:
    energy_balance!(object_tmp, meteo, constants)

    # --- Extracting the outputs: ---

    # Pre-allocating the outputs:
    output = Dict([k => v.status for (k, v) in object_tmp])

    for (k, v) in object_tmp
        output[k] = v.status
    end

    output = DataFrame([(NamedTuple(v)..., component = k) for (k, v) in output])

    return output
end

# energy_balance over several meteo time steps (called Weather) and possibly several components.
# Only allowed for components given as a subtype of AbstractDict to track components names in the
# outputs
function energy_balance!(
    object::T,
    meteo::Weather,
    constants = Constants()
    ) where T <: Union{AbstractDict{N,<:AbstractComponentModel} where N}

    # Pre-allocating the time-step outputs:
    timestep_tmp = Dict([k => v.status for (k, v) in object])

    # Pre-allocating the general DataFrame with the first time-step results:
    energy_balance!(object, meteo.data[1], constants)

    for (k, v) in object
        timestep_tmp[k] = v.status
    end

    output_timestep = DataFrame([(NamedTuple(v)..., component = k) for (k, v) in timestep_tmp])

    # Actually pre-allocating the DF:
    output = repeat(output_timestep, length(meteo.data))
    output.time_step = repeat(1:length(meteo.data), inner = size(output_timestep, 1))

    # Computing for all following time-steps:
    for (i, meteo_i) in enumerate(meteo.data[2:end])
        energy_balance!(object, meteo_i, constants)

        for (k, v) in object
            timestep_tmp[k] = v.status
        end

        output_timestep = DataFrame([(NamedTuple(v)..., component = k) for (k, v) in timestep_tmp])

        # Update the values of the global output:
        output[output.time_step .== i,Not(:time_step)] = output_timestep
    end

    return output
end

# energy_balance over several meteo time steps (same as above) but non-mutating
function energy_balance(
    object::T,
    meteo::Weather,
    constants = Constants()
    ) where T <: Union{AbstractDict{N,<:AbstractComponentModel} where N}

    object_tmp = copy(object)

    return energy_balance!(object_tmp, meteo, constants)
end


export defΩ, SimpleΩ, SimpleΩ

using ..IDS, ..Util, ..Tagging
using Random: AbstractRNG
import ..Basis: replacetags
import ..Var

"Simplest, immutable Omega"
struct SimpleΩ{TAGS <: Tags, T, S} <: AbstractΩ
  data::T
  tags::TAGS
  subspace::S
end

Basis.subspace(ω::SimpleΩ) = ω.subspace

# AbstractDict interface
Base.keys(ω::SimpleΩ) = Base.keys(ω.data)
Base.values(ω::SimpleΩ) = Base.values(ω.data)
Base.pairs(ω::SimpleΩ) = Base.pairs(ω.data)
Base.getindex(ω::SimpleΩ, id) = ω.data[id]

idtype(::SimpleΩ{TAGS, Dict{T, V}}) where {TAGS, T, V} = T

SimpleΩ(data) = SimpleΩ(data, Tags())
replacetags(ω::SimpleΩ, tags) = SimpleΩ(ω.data, tags)
# (T::Type{<:Distribution})(π::SimpleΩ, args...) = ω.data[scope(ω)]

function Var.recurse(exo::Var.PrimRandVar, ω::SimpleΩ)
  newid = append(ω.subspace, exo.id)
  newexo = Var.Member(newid, exo.class)
  ω.data[newexo]::eltype(newexo.class)
end

traithastag(t::Type{SimpleΩ{TAGS, T}}, tag) where {TAGS, T} = traithastag(TAGS, tag)
traits(::Type{SimpleΩ{TAGS, T}}) where {TAGS, T} = traits(TAGS)

function resolve(dist, id, ω::SimpleΩ)
  # @assert false
  @show ω
  @show id
  @show ω.data[id]
  d, val = ω.data[id]
  val::eltype(dist)
end


function MLJModelInterface.fit(model::EvoTypes, verbosity::Int, X, y)
    Xmatrix = MLJModelInterface.matrix(X)
    fitresult, cache = init_evotree(model, Xmatrix, y, verbosity = verbosity)
    grow_evotree!(fitresult, cache, verbosity = verbosity)
    report = nothing
    return fitresult, cache, report
end

function okay_to_continue(new, old)
    new.nrounds - old.nrounds >= 0 &&
    new.loss == old.loss &&
    new.λ == old.λ &&
    new.γ == old.γ &&
    new.max_depth  == old.max_depth &&
    new.min_weight == old.min_weight &&
    new.rowsample ==  old.rowsample &&
    new.colsample ==  old.colsample &&
    new.nbins ==  old.nbins &&
    new.α ==  old.α &&
    new.metric ==  old.metric
end

function MLJModelInterface.update(model::EvoTypes, verbosity::Integer, fitresult, cache, X, y)

    if okay_to_continue(model, cache.params)
        grow_evotree!(fitresult, cache, verbosity = verbosity)
    else
        Xmatrix = MLJModelInterface.matrix(X)
        fitresult, cache = init_evotree(model, Xmatrix, y, verbosity = verbosity)
        grow_evotree!(fitresult, cache, verbosity = verbosity)
    end
    report = nothing
    return fitresult, cache, report
end

function predict(model::EvoTreeRegressor, fitresult, Xnew)
    Xnew = MLJModelInterface.matrix(Xnew)
    pred = predict(fitresult, Xnew)
    return pred
end

function predict(model::EvoTreeClassifier, fitresult, Xnew)
    Xnew = MLJModelInterface.matrix(Xnew)
    pred = predict(fitresult, Xnew)
    return [MLJModelInterface.UnivariateFinite(fitresult.levels, pred[i,:]) for i in 1:size(pred,1)]
end

function predict(model::EvoTreeCount, fitresult, Xnew)
    Xnew = MLJModelInterface.matrix(Xnew)
    λ = predict(fitresult, Xnew)
    return [Distributions.Poisson(λᵢ) for λᵢ ∈ λ]
end

function predict(model::EvoTreeGaussian, fitresult, Xnew)
    Xnew = MLJModelInterface.matrix(Xnew)
    pred = predict(fitresult, Xnew)
    return [Distributions.Normal(pred[i]...) for i in 1:size(pred,1)]
end

# Metadata
const EvoTreeRegressor_desc = "Regression models with various underlying methods: least square, quantile, logistic."
const EvoTreeClassifier_desc = "Multi-classification with softmax and cross-entropy loss."
const EvoTreeCount_desc = "Poisson regression fitting λ with max likelihood."
const EvoTreeGaussian_desc = "Gaussian maximum likelihood of μ and σ²."

MLJModelInterface.metadata_pkg.((EvoTreeRegressor, EvoTreeClassifier, EvoTreeCount, EvoTreeGaussian),
    name="EvoTrees",
    uuid="f6006082-12f8-11e9-0c9c-0d5d367ab1e5",
    url="https://github.com/Evovest/EvoTrees.jl",
    julia=true,
    is_wrapper=false)

MLJModelInterface.metadata_model(EvoTreeRegressor,
    input=MLJModelInterface.Table(MLJModelInterface.Continuous),
    target=AbstractVector{<:MLJModelInterface.Continuous},
    weights=false,
    descr=EvoTreeRegressor_desc)

MLJModelInterface.metadata_model(EvoTreeClassifier,
        input=MLJModelInterface.Table(MLJModelInterface.Continuous),
        target=AbstractVector{<:MLJModelInterface.Finite},
        weights=false,
        descr=EvoTreeClassifier_desc)

MLJModelInterface.metadata_model(EvoTreeCount,
    input=MLJModelInterface.Table(MLJModelInterface.Continuous),
    target=AbstractVector{<:MLJModelInterface.Count},
    weights=false,
    descr=EvoTreeCount_desc)

MLJModelInterface.metadata_model(EvoTreeGaussian,
    input=MLJModelInterface.Table(MLJModelInterface.Continuous),
    target=AbstractVector{<:MLJModelInterface.Continuous},
    weights=false,
    descr=EvoTreeGaussian_desc)

# shared metadata
# MLJModelInterface.package_name(::Type{<:EvoTypes}) = "EvoTrees"
# MLJModelInterface.package_uuid(::Type{<:EvoTypes}) = "f6006082-12f8-11e9-0c9c-0d5d367ab1e5"
# MLJModelInterface.package_url(::Type{<:EvoTypes}) = "https://github.com/Evovest/EvoTrees.jl"
# MLJModelInterface.is_pure_julia(::Type{<:EvoTypes}) = true
#
# MLJModelInterface.load_path(::Type{<:EvoTreeRegressor}) = "EvoTrees.EvoTreeRegressor"
# MLJModelInterface.input_scitype(::Type{<:EvoTreeRegressor}) = MLJModelInterface.Table(MLJModelInterface.Continuous)
# MLJModelInterface.target_scitype(::Type{<:EvoTreeRegressor}) = AbstractVector{<:MLJModelInterface.Continuous}
#
# MLJModelInterface.load_path(::Type{<:EvoTreeCount}) = "EvoTrees.EvoTreeCount"
# MLJModelInterface.input_scitype(::Type{<:EvoTreeCount}) = MLJModelInterface.Table(MLJModelInterface.Continuous)
# MLJModelInterface.target_scitype(::Type{<:EvoTreeCount}) = AbstractVector{<:MLJModelInterface.Count}
#
# MLJModelInterface.load_path(::Type{<:EvoTreeClassifier}) = "EvoTrees.EvoTreeClassifier"
# MLJModelInterface.input_scitype(::Type{<:EvoTreeClassifier}) = MLJModelInterface.Table(MLJModelInterface.Continuous)
# MLJModelInterface.target_scitype(::Type{<:EvoTreeClassifier}) = AbstractVector{<:MLJModelInterface.Finite}
#
# MLJModelInterface.load_path(::Type{<:EvoTreeGaussian}) = "EvoTrees.EvoTreeGaussian"
# MLJModelInterface.input_scitype(::Type{<:EvoTreeGaussian}) = MLJModelInterface.Table(MLJModelInterface.Continuous)
# MLJModelInterface.target_scitype(::Type{<:EvoTreeGaussian}) = AbstractVector{<:MLJModelInterface.Continuous}

# function MLJ.clean!(model::EvoTreeRegressor)
#     warning = ""
#     if model.nrounds < 1
#         warning *= "Need nrounds ≥ 1. Resetting nrounds=1. "
#         model.nrounds = 1
#     end
#     if model.λ < 0
#         warning *= "Need λ ≥ 0. Resetting λ=0. "
#         model.λ = 0.0
#     end
#     if model.γ < 0
#         warning *= "Need γ ≥ 0. Resetting γ=0. "
#         model.γ = 0.0
#     end
#     if model.η <= 0
#         warning *= "Need η > 0. Resetting η=0.001. "
#         model.η = 0.001
#     end
#     if model.max_depth < 1
#         warning *= "Need max_depth ≥ 0. Resetting max_depth=0. "
#         model.max_depth = 1
#     end
#     if model.min_weight < 0
#         warning *= "Need min_weight ≥ 0. Resetting min_weight=0. "
#         model.min_weight = 0.0
#     end
#     if model.rowsample < 0
#         warning *= "Need rowsample ≥ 0. Resetting rowsample=0. "
#         model.rowsample = 0.0
#     end
#     if model.rowsample > 1
#         warning *= "Need rowsample <= 1. Resetting rowsample=1. "
#         model.rowsample = 1.0
#     end
#     if model.colsample < 0
#         warning *= "Need colsample ≥ 0. Resetting colsample=0. "
#         model.colsample = 0.0
#     end
#     if model.colsample > 1
#         warning *= "Need colsample <= 1. Resetting colsample=1. "
#         model.colsample = 1.0
#     end
#     if model.nbins > 250
#         warning *= "Need nbins <= 250. Resetting nbins=250. "
#         model.nbins = 250
#     end
#     return warning
# end


export A2CGAELearner

"""
    A2CGAELearner(;kwargs...)
# Keyword arguments
- `approximator`, an [`ActorCritic`](@ref) based [`NeuralNetworkApproximator`](@ref)
- `γ::Float32`, reward discount rate.
- `λ::Float32`, lambda for GAE-lambda
- `actor_loss_weight::Float32`
- `critic_loss_weight::Float32`
- `entropy_loss_weight::Float32`
"""
Base.@kwdef mutable struct A2CGAELearner{A<:ActorCritic} <: Any
    approximator::A
    γ::Float32
    λ::Float32
    max_grad_norm::Union{Nothing,Float32} = nothing
    actor_loss_weight::Float32
    critic_loss_weight::Float32
    entropy_loss_weight::Float32
    update_freq::Int
    update_step::Int = 0
    # for logging
    actor_loss::Float32 = 0.0f0
    critic_loss::Float32 = 0.0f0
    entropy_loss::Float32 = 0.0f0
    loss::Float32 = 0.0f0
    norm::Float32 = 0.0f0
end

Functors.functor(x::A2CGAELearner) =
    (app = x.approximator,), y -> @set x.approximator = y.app

(learner::A2CGAELearner)(env::MultiThreadEnv) =
    learner.approximator.actor(send_to_device(device(learner), state(env))) |> send_to_host

function RLBase.update!(learner::A2CGAELearner, t::CircularArraySARTTrajectory)
    length(t) == 0 && return  # in the first update, only state & action is inserted into trajectory
    learner.update_step += 1
    if learner.update_step % learner.update_freq == 0
        _update!(learner, t)
    end
end

function _update!(learner::A2CGAELearner, t::CircularArraySARTTrajectory)
    n = length(t)

    AC = learner.approximator
    to_device(x) = send_to_device(device(AC), x)
    γ = learner.γ
    λ = learner.λ
    w₁ = learner.actor_loss_weight
    w₂ = learner.critic_loss_weight
    w₃ = learner.entropy_loss_weight

    S = t[:state] |> to_device
    # (state_size..., n_thread * update_step)
    states_flattened = select_last_dim(S, 1:n) |> flatten_batch

    actions =
        t[:action] |>
        x ->
            select_last_dim(x, 1:n) |> flatten_batch |> a -> CartesianIndex.(a, 1:length(a))

    rollout_values =
        S |> flatten_batch |> AC.critic |> x -> reshape(x, :, n + 1) |> send_to_host

    advantages = generalized_advantage_estimation(
        t[:reward],
        rollout_values,
        γ,
        λ;
        dims = 2,
        terminal = t[:terminal],
    )

    gains = to_device(advantages + select_last_dim(rollout_values, 1:n))

    advantages = advantages |> flatten_batch |> to_device

    ps = Flux.params(AC)
    gs = gradient(ps) do
        logits = AC.actor(states_flattened)
        probs = softmax(logits)
        log_probs = logsoftmax(logits)
        log_probs_select = log_probs[actions]
        values = AC.critic(states_flattened)
        advantage = vec(gains) .- vec(values)
        actor_loss = -mean(log_probs_select .* advantages)
        critic_loss = mean(advantage .^ 2)
        entropy_loss = -sum(probs .* log_probs) * 1 // size(probs, 2)
        loss = w₁ * actor_loss + w₂ * critic_loss - w₃ * entropy_loss
        ignore() do
            learner.actor_loss = actor_loss
            learner.critic_loss = critic_loss
            learner.entropy_loss = entropy_loss
            learner.loss = loss
        end
        loss
    end

    if !isnothing(learner.max_grad_norm)
        learner.norm = clip_by_global_norm!(gs, ps, learner.max_grad_norm)
    end

    update!(AC, gs)
end

RLCore.check(::QBasedPolicy{<:A2CGAELearner}, ::MultiThreadEnv) = nothing


module ColoredGraphs

using LightGraphs
using MacroTools: @forward

import Base:
    eltype, show, ==, Pair, Tuple, copy, length, issubset, reverse, zero, in, iterate

import LightGraphs:
    _NI, AbstractGraph, AbstractEdge, AbstractEdgeIter,
    src, dst, edgetype, nv, ne, vertices, edges, is_directed,
    has_vertex, has_edge, inneighbors, outneighbors,

    indegree, outdegree, degree, has_self_loops, num_self_loops, insorted

export ColoredGraph, ColoredDiGraph, ColoredEdge

export HyperCube, translational_symm_table

"""
    AbstractColoredEdge{T} <: AbstractEdge{T}

Abstract base type for an edge with a color.
"""
abstract type AbstractColoredEdge{T} <: AbstractEdge{T} end

"""
    AbstractColoredGraph{T} <: AbstractGraph{T}

Abstract base type for colored graphs.
"""
abstract type AbstractColoredGraph{T} <: AbstractGraph{T} end

include("ColoredGraphs/ColoredEdge.jl")
include("ColoredGraphs/ColoredEdgeIter.jl")
include("ColoredGraphs/ColoredGraph.jl")

include("WrappedSimpleGraphs/AbstractWrappedSimpleGraph.jl")
include("WrappedSimpleGraphs/HyperCube.jl")

end # module


include("types.jl")
include("functions.jl")
include("units.jl")
include("gamefuns.jl")
include("newfuns.jl")

function distance(loc1,loc2=(0,0,2))
	oloc=loc1.-loc2
	m=max(abs(oloc[1]),abs(oloc[2]))
	return max(m,abs(oloc[1]+oloc[2]))+oloc[3]
end
function save(game)
	dic=Dict()
	dic[:name]=game.name
	dic[:shells]=game.board.shells
	dic[:initlocs]=game.board.initlocs
	dic[:colind]=game.colind
	dic[:colmax]=game.colmax
	dic[:colock]=game.colock
	dic[:delete]=game.delete
	dic[:sequence]=[]
	for entry in game.sequence
		if isa(entry,Unit)
			unit=entry
		#elseif !isa(entry,Symbol) && length(entry)>1 && isa(entry[2],Unit)
		#	unit=entry[2]
		else
			push!(dic[:sequence],entry)
			continue
		end
		udic=Dict()
		udic[:name]=unit.name
		udic[:color]=unit.color
		udic[:loc]=unit.loc
		push!(dic[:sequence],udic)
	end
	dir=joinpath(homedir(),"weilianqi","saves",game.name)
	if !ispath(dir)
		touch(dir)
	end
	io=open(dir,"a+")
	write(io,string(dic),"\n")
	close(io)
	println("Saved $(length(game.units)) units at $dir")
end
function loadsequence!(game::Game,seq,originoffset=(0,0,0))
	for entry in seq
		if entry==:harvest
			harvest!(game)
		elseif entry[1]==:expand
			expandboard!(game,entry[2]...,false)
		else
			(loc,unit)=entry
			loco=loc.+originoffset
			placeunit!(game,unit)
		end
	end
	return "<3"
end
function loadic!(game,dic::Dict,originoffset=(0,0,0))
	#game.board=newboard(dic[:shells],dic[:initlocs]) #seeeegfault
	if haskey(dic,:colind)
		game.colind=dic[:colind]
	end
	game.color=game.colors[game.colind]
	if haskey(dic,:colmax)
		game.colmax=dic[:colmax]
	end
	if haskey(dic,:colock)
		game.colock=dic[:colock]
	end
	set_gtk_property!(game.gui[:colockcheck],:active,game.colock)
	if haskey(dic,:delete)
		game.delete=dic[:delete]
	end
	set_gtk_property!(game.gui[:deletecheck],:active,game.delete)
	if haskey(dic,:initlocs) #&& game.board.initlocs!=dic[:initlocs]
		pushfirst!(dic[:sequence],(:expand,[dic[:shells],dic[:initlocs]])) #let's hope this works
	end
	for entry in dic[:sequence]
		if isa(entry,Dict)
			if entry[:name]=="spawn";entry[:name]="queen";end
			unit=newunit(entry)
			unit.loc=unit.loc.+originoffset
			placeunit!(game,unit)
		elseif entry==:harvest
			harvest!(game)
		elseif entry[1]==:expand
			for ili in 1:length(entry[2][2])
				entry[2][2][ili]=entry[2][2][ili].+originoffset
			end
			expandboard!(game,entry[2][1],entry[2][2],false) #loadicing
		elseif entry[1]==:delete
			removeunit!(game,entry[2])
		end
	end
	sync!(game)
	return game
end
function loadic(dic::Dict,originoffset=(0,0,0))
	game=newgame(dic[:name],[dic[:shells],dic[:initlocs]],sequence=[])
	loadic!(game,dic,originoffset)
	return game
end
function string2game(lastlineofsavefile)
	dic=eval(parse(lastlineofsavefile))
	return loadic(dic)
end
function loadgame!(game::Game,name::String,originoffset=(0,0,0),backtrack::Integer=0)
	path=joinpath(homedir(),"weilianqi","saves",name)
	lines=readlines(path)
	dic=eval(Meta.parse(lines[end-backtrack]))
	loadic!(game,dic,originoffset)
	return game
end
function loadgame(name::String,backtrack::Integer=0)
	path=joinpath(homedir(),"weilianqi","saves",name)
	lines=readlines(path)
	dic=eval(Meta.parse(lines[end-backtrack]))
	if isa(dic,Dict)
		game=loadic(dic)
	else
		game=newgame(name)
		loadsequence!(game,dic)
	end
	sync!(game)
	return game
end


using ArgParse
using BSON
using ConvHMM
using Distributed
using ExpFamilyDistributions
using Glob
using MarkovModels
using NaturalSort

# BUT specific package to work with the SGE cluster
# See https://github.com/BUTSpeechFIT/BUTSGEManager
using BUTSGEManager

function getargs()
    s = ArgParseSettings()
    @add_arg_table! s begin
        "--epochs", "-e"
            help = "number of epochs to train"
            arg_type = Int
            default = 1
        "--init", "-i"
            help = "initial model to compute the alignments"
            arg_type = String
            default = ""
        "--update-prec-rate", "-u"
            help = "update rate of the precision parameter"
            arg_type = Int
            default = 10
        "--jobs", "-j"
            help = "number of jobs to run in parallel"
            arg_type = Int
            default = 4
        "--jobs-args", "-a"
            help = "arguments to pass to the parallel engine"
            arg_type = String
            default = ""
        "uttids"
            help = "list of utterance ids to train on"
            arg_type = String
            required = true
        "feadir"
            help = "features directory"
            arg_type = String
            required = true
        "alidir"
            help = "alignment graphs directory"
            arg_type = String
            required = true
        "modeldir"
            help = "directory where was created the initial model"
            arg_type = String
            required = true
    end
    args = parse_args(s)
end

const args = getargs()
const initfile = args["init"]
const updaterate = args["update-prec-rate"]
const uttids = readlines(args["uttids"])
const feadir = args["feadir"]
const alidir = args["alidir"]
const outdir = args["modeldir"]
const epochs = args["epochs"]

const init = initfile == "" ? nothing : ConvHMM.load(initfile)

# Make sure the directory exists
run(`mkdir -p $outdir`)

########################################################################
# Setup the workers
@info "initializing the workers"
addprocs(SGEManager(args["jobs"]), args = args["jobs-args"],
         exeflags = "--project=$(Base.active_project())")

@everywhere using BSON
@everywhere using ExpFamilyDistributions
@everywhere using MarkovModels
@everywhere using ConvHMM
@everywhere const args = $args
@everywhere const feadir = $feadir
@everywhere const alidir = $alidir
@everywhere const init = $init
@everywhere const updaterate = $updaterate

########################################################################
# Functions to accumulate statistics and to update the parameters

# Sum dictionaries fo statistics
@everywhere function mergestats(
    s1::Dict{Int64, Dict{Int64, Vector{Float64}}},
    s2::Dict{Int64, Dict{Int64, Vector{Float64}}}
)
    retval = Dict{Int64, Dict{Int64, Vector{Float64}}}()
    for d in keys(s1)
        retval[d] = Dict{Int64, Vector{Float64}}()
        for s in keys(s1[d])
            retval[d][s] = s1[d][s] + s2[d][s]
        end
   end
   retval
end

@everywhere function accumulate_h(
    model::Vector{Vector{ARNormal1D{K}}},
    X::Matrix{T},
    γ::Matrix{T}
) where {K, T <: AbstractFloat}

    D = length(model)
    S = length(model[1])

    accstats = Dict{Int64, Dict{Int64, Vector{Float64}}}()
    for d in 1:D
        accstats[d] = Dict{Int64, Vector{Float64}}()
        for (s, stats) in zip(1:S, accstats_h(model[d], X[d, :], γ))
            accstats[d][s] = stats
        end
    end
    accstats
end

@everywhere function accumulate_λ(
    model::Vector{Vector{ARNormal1D{K}}},
    X::Matrix{T},
    γ::Matrix{T}
) where {K, T <: AbstractFloat}

    D = length(model)
    S = length(model[1])

    accstats = Dict{Int64, Dict{Int64, Vector{Float64}}}()
    for d in 1:D
        accstats[d] = Dict{Int64, Vector{Float64}}()
        for (s, stats) in zip(1:S, accstats_λ(model[d], X[d, :], γ))
            accstats[d][s] = stats
        end
    end
    accstats
end

function update_h!(
    model::Vector{Vector{ARNormal1D{K}}},
    stats::Dict{Int64, Dict{Int64, Vector{T}}},
) where {K, T <: AbstractFloat}

    D = length(model)
    S = length(model[1])
    for d in 1:D
        for s in 1:S
            m = model[d][s]
            s = stats[d][s]
            η₀ = naturalparam(m.hprior)
            update!(m.hposterior, η₀ .+ s)
        end
    end
end

function update_λ!(
    model::Vector{Vector{ARNormal1D{K}}},
    stats::Dict{Int64, Dict{Int64, Vector{T}}},
) where {K, T <: AbstractFloat}

    D = length(model)
    S = length(model[1])
    for d in 1:D
        for s in 1:S
            m = model[d][s]
            s = stats[d][s]
            η₀ = naturalparam(m.λprior)
            update!(m.λposterior, η₀ .+ s)
        end
    end
end

########################################################################
# Training
#
# NOTE: the training is an EM-like algorithm. For the M-step, we
# alternately update the filters (h) or the precision parameters (λ)

# KL-divergence post/prior of the parameters of the model
function kldiv_post(model)
    KL = 0.
    for d in 1:length(model)
        for m in model[d]
            KL += kldiv(m.hposterior, m.hprior)
            KL += kldiv(m.λposterior, m.λprior)
        end
    end
    KL
end

# Retrieve the last check-point
fnames = [basename(fname) for fname in glob(joinpath(outdir, "emissions_*bson"))]
fnames = sort(fnames, lt=natural)
epochno = replace(replace(fnames[end], ("emissions_" => "")), (".bson" => ""))
start = parse(Int64, epochno) + 1

if start > 1
    @info "found $(fnames[end]), starting training from epoch $start"
end

@everywhere reducer(a, b) = (mergestats(a[1], b[1]), a[2] + b[2], a[3] + b[3])
for e in start:epochs
    path = joinpath(outdir, "emissions_$(e-1).bson")
    @everywhere emissions = ConvHMM.load($path)

    @everywhere epoch = $e
    stats, totll, totN = @distributed reducer for uttid in uttids
        X = BSON.load(joinpath(feadir, uttid * ".bson"))[:data]
        ali = BSON.load(joinpath(alidir, uttid * ".bson"))[:ali]

        S = length(emissions[1])
        N = size(X, 2)

        # E-step

        # Special case if this is the first epoch and the user has
        # specified a model for initialization
        alimodel = epoch == 1 && ! isnothing(init) ? init : emissions

        lnαβ, totll =   αβrecursion(ali, alimodel(X))
        γ_sparse = resps(ali, lnαβ, dense = false)
        γ = zeros(S, N)
        for idx in keys(γ_sparse) γ[idx, :] = γ_sparse[idx] end

        if e % updaterate > 0
            accstats = accumulate_h(emissions, X, γ)
        else
            accstats = accumulate_λ(emissions, X, γ)
        end

        (accstats, totll, N)
    end

    kl = kldiv_post(emissions)
    𝓛 = (totll - kl) / totN
    @info "epoch $(e)/$(epochs) 𝓛 = $(round(𝓛, digits = 3)) llh = $(round(totll, digits = 3)) KL = $(round(kl, digits=3))"

    if e % updaterate > 0
        update_h!(emissions, stats)
    else
        update_λ!(emissions, stats)
    end

    save(joinpath(outdir, "emissions_$(e).bson"), emissions)
end


@testset "runtestString" begin
    println("Testing : runtestString")

    include("../src/bfgaHelloWorld.jl")
    using .bfgaHelloWorld

    include("../src/Bf.jl")
    using .BfInterpreter

    goal = bfgaHelloWorld.getTarget()
    target_length = length(goal)

    @time model = test_serial()
    prems = model.refSet[length(model.refSet)]
    println("Generation : $(model.params.currentGeneration)")
    println("& fitness : $(model.specific_fitness.fitness(prems, model.instructionsSet))")
    #bfcode :: Array{Float64,1}  = prems.dna
    println("code : $(prems.program) ") #$(join(genesToBfInstructions(bfcode), "") ) ")

    #bft = bfType(bfcode)

    output, _ = execute(prems.program, model.instructionsSet)
    #output2 = execute(bft, input2)

    @test length(output) >= target_length
    @test collect(goal) == output[1:target_length]

    # do the test_parallel after ...
    #test_parallel()
end


"""
    arbitraryparam(params)

returns the first field in the struct that is a subtype of `ClapeyronParam`. errors if it finds none.
"""
function arbitraryparam(params)
    paramstype = typeof(params)
    idx = findfirst(z->z <: ClapeyronParam,fieldtypes(paramstype))
    if isnothing(idx)
        error("The paramater struct ", paramstype, " must contain at least one ClapeyronParam")
    end
     return fieldnames(paramstype)[idx] |> z->getfield(params,z)
end

"""
    @comps

This macro is an alias to

    model.icomponents

The caveat is that `model` has to exist in the local namespace.
`model` is expected to be an EoSModel type that contains the `icomponents` field.
`icomponents` is an iterator that goes through all component indices.
"""
macro comps()
    return quote
        1:length(model)
    end |> esc
end

"""
    @groups

This macro is an alias to

    model.groups.i_flattenedgroups

`iflattenedgroups` is an iterator that goes through all groups in flattenedgroups.
"""
macro groups()
    return :($(esc(:(model.groups.i_flattenedgroups::UnitRange{Int64}))))
end

"""
    @groups(component)

This macro is an alias to

    model.groups.i_groups[component]

`i_groups[component]` is an iterator that goes through all groups in relevent to a given component.
"""
macro groups(component)
    return :($(esc(:(model.groups.i_groups[$(component)]::Vector{Int}))))
end

"""
    @sites(component)

This macro is an alias to

    model.sites.i_sites[component]

`i_sites[component]` is an iterator that goes through all sites relevant to
each group in a GC model, and to each main component in a non-GC model.
"""
macro sites(component)
    return :($(esc(:(model.sites.i_sites[$(component)]::Vector{Int}))))
end

"""
    @f(func,a,b,c,...)

This macro is an alias to

    func(model, V, T, z, a, b, c, ...)

where `func` is the name of the function, `model` is the model struct,
`V` is the volume, `T` is the absolute temperature, `z` is an array of number
of moles of each component, and `a`, `b`, `c`, ... are arbitrary parameters
that get passed to `func`.

It is very common for functions that are involved in the models to contain the
`model`, `V`, `T` and `z` parameters, so this macro helps reduce code repetition
as long as the first four parameters in the function are written exactly as above.

"""
macro f(func, args...)
    f = func
    model = :model
    V = :V
    T = :T
    z = :z
    quote
        $f($model,$V,$T,$z,$(args...))
    end |> esc
end

"""
    @nan(function_call,default=NaN)

Wraps the function in a `try-catch` block, and if a `DomainError` or `DivideError` is raised, then returns `default`.
for better results, its best to generate the default result beforehand
"""
macro nan(Base.@nospecialize(fcall),default = nothing)
    e = gensym(:error)
    quote
      try $fcall
      catch $e
        if $e isa Union{DomainError,DivideError}
          $default
        else
          rethrow($e)
        end
      end
    end |> esc
  end

"""
    @newmodelgc modelname parent paramstype

This is a data type that contains all the information needed to use an EoS model.
It also functions as an identifier to ensure that the right functions are called.

The user is expected to create an outter constructor that takes this signature

    function modelname(components::Array{String,1})

It should then return name(params::paramtype, groups::GroupParam, sites::SiteParam, idealmodel::IdealModel)

= Fields =
The Struct consists of the following fields:

* components: a string lists of components
* icomponents: an iterator that goes through the indices corresponding to each component
* groups: a [`GroupParam`](@ref)
* sites: a [`SiteParam`](@ref)
* params: the Struct paramstype that contains all parameters in the model
* idealmodel: the IdealModel struct that determines which ideal model to use
* assoc_options: struct containing options for the association solver. see [AssocOptions](@ref)
* references: reference for this EoS

See the tutorial or browse the implementations to see how this is used.
"""
macro newmodelgc(name, parent, paramstype)
    quote
    struct $name{T <: IdealModel} <: $parent
        components::Array{String,1}
        icomponents::UnitRange{Int}
        groups::GroupParam
        sites::SiteParam
        params::$paramstype
        idealmodel::T
        assoc_options::AssocOptions
        references::Array{String,1}
    end

    has_sites(::Type{<:$name}) = true
    has_groups(::Type{<:$name}) = true

    function Base.show(io::IO, mime::MIME"text/plain", model::$name)
        return gc_eosshow(io, mime, model)
    end

    function Base.show(io::IO, model::$name)
        return eosshow(io, model)
    end

    Base.length(model::$name) = Base.length(model.icomponents)

    molecular_weight(model::$name,z=SA[1.0]) = group_molecular_weight(model.groups,mw(model),z)

end |> esc
end

"""

    @newmodel name parent paramstype

This is exactly the same as the above but for non-GC models.
All group parameters are absent in this struct.
The sites are associated to the main component rather than the groups,
and the respective fieldnames are named correspondingly.
"""
macro newmodel(name, parent, paramstype)
    quote
    struct $name{T <: IdealModel} <: $parent
        components::Array{String,1}
        icomponents::UnitRange{Int}
        sites::SiteParam
        params::$paramstype
        idealmodel::T
        assoc_options::AssocOptions
        references::Array{String,1}
    end
    has_sites(::Type{<:$name}) = true
    has_groups(::Type{<:$name}) = false

    function Base.show(io::IO, mime::MIME"text/plain", model::$name)
        return eosshow(io, mime, model)
    end

    function Base.show(io::IO, model::$name)
        return eosshow(io, model)
    end
    molecular_weight(model::$name,z=SA[1.0]) = comp_molecular_weight(mw(model),z)
    Base.length(model::$name) = Base.length(model.icomponents)
    end |> esc
end

"""
    @newmodelsimple

Even simpler model, primarily for the ideal models.
Contains neither sites nor ideal models.
"""
macro newmodelsimple(name, parent, paramstype)
    quote
    struct $name <: $parent
        components::Array{String,1}
        icomponents::UnitRange{Int}
        params::$paramstype
        references::Array{String,1}
    end
    has_sites(::Type{<:$name}) = false
    has_groups(::Type{<:$name}) = false

    function Base.show(io::IO, mime::MIME"text/plain", model::$name)
        return eosshow(io, mime, model)
    end

    function Base.show(io::IO, model::$name)
        return eosshow(io, model)
    end

    Base.length(model::$name) = Base.length(model.icomponents)

    end |> esc
end

const IDEALTYPE = Union{T,Type{T}} where T<:IdealModel

function (::Type{model})(params::EoSParam,
        groups::GroupParam,
        sites::SiteParam,
        idealmodel::IDEALTYPE = BasicIdeal;
        ideal_userlocations::Vector{String}=String[],
        references::Vector{String}=String[],
        assoc_options::AssocOptions = AssocOptions(),
        verbose::Bool = false) where model <:EoSModel

        components = groups.components
        icomponents = 1:length(components)
        init_idealmodel = init_model(idealmodel,components,ideal_userlocations,verbose)
        return model(components, icomponents,
        groups,
        sites,
        params, init_idealmodel, assoc_options, references)
end

function (::Type{model})(params::EoSParam,
        groups::GroupParam,
        idealmodel::IDEALTYPE = BasicIdeal;
        ideal_userlocations::Vector{String}=String[],
        references::Vector{String}=String[],
        assoc_options::AssocOptions = AssocOptions(),
        verbose::Bool = false) where model <:EoSModel

    sites = SiteParam(groups.components)
    return model(params,groups,sites,idealmodel;ideal_userlocations,references,assoc_options,verbose)
end

#non GC
function (::Type{model})(params::EoSParam,
        sites::SiteParam,
        idealmodel::IDEALTYPE = BasicIdeal;
        ideal_userlocations::Vector{String}=String[],
        references::Vector{String}=String[],
        assoc_options::AssocOptions = AssocOptions(),
        verbose::Bool = false) where model <:EoSModel

    components = sites.components
    icomponents = 1:length(components)

    init_idealmodel = init_model(idealmodel,components,ideal_userlocations,verbose)
    return model(components, icomponents,
    sites, params, init_idealmodel, assoc_options, references)

end

#non GC may be shared with model simple
function (::Type{model})(params::EoSParam,
        idealmodel::IDEALTYPE = BasicIdeal;
        ideal_userlocations::Vector{String}=String[],
        references::Vector{String}=String[],
        assoc_options::AssocOptions = AssocOptions(),
        verbose::Bool = false) where model <:EoSModel

    if has_sites(model)
        arbparam = arbitraryparam(params)
        components = arbparam.components
        sites = SiteParam(components)
        return model(params,sites,idealmodel;ideal_userlocations,references,assoc_options,verbose)
    end
    #With sites out of the way, this is a simplemodel, no need to initialize the ideal model
    #if there isnt any params, just put empty values.
    if length(fieldnames(typeof(params))) > 0
    arbparam = arbitraryparam(params)
    components = arbparam.components
    icomponents = 1:length(components)
    else
        components = String[]
        icomponents =1:0
    end
    return model(components,icomponents,params,references)
end

function init_model(idealmodel::IdealModel,components,userlocations,verbose)
    return idealmodel
end

function init_model(::Nothing,components,userlocations,verbose)
    return nothing
end

function init_model(idealmodel::Type{<:IdealModel},components,userlocations,verbose)
    verbose && @info("""Now creating ideal model:
    $idealmodel""")
    return idealmodel(components;userlocations,verbose)
end
"""
    @registermodel(model)

given an existing model, composed of Clapeyron EoS models, ClapeyronParams or EoSParams, it will generate
the necessary traits to make the model compatible with Clapeyron routines.

"""
macro registermodel(model)
    _model = @eval $model
    _has_components = hasfield(_model,:components)
    splittable = _has_components

    _has_sites = hasfield(_model,:sites)
    _has_groups = hasfield(_model,:groups)
    _eos_show = if _has_groups
        :(gc_eosshow(io, mime, model))
    else
        :(eosshow(io, mime, model))
    end

    _len = if hasfield(_model,:icomponents)
        :(Base.length(model.icomponents))
    else
        :(Base.length(model.components))
    end

    _length = if _has_components
            :(Base.length(model::$model) = $_len)
        else
            :()
        end

    _mw = if _has_groups
        :(group_molecular_weight(model.groups,mw(model),z))
    else
        :(comp_molecular_weight(mw(model),z))
    end

    _molecular_weight = if _has_components
        :(molecular_weight(model::$model,z=SA[1.0]) =$_mw)
    else
        :()
    end

return quote
    has_sites(::Type{<:$model}) = $_has_sites
    has_groups(::Type{<:$model}) = $_has_groups

    function Base.show(io::IO, mime::MIME"text/plain", model::$model)
        return $_eos_show
    end

    function Base.show(io::IO, model::$model)
        return eosshow(io, model)
    end

    $_length

    $_molecular_weight

    end |> esc
end

export @newmodel, @f, @newmodelgc, @newmodelsimple


# This code is from https://github.com/JuliaAudio/SampledSignals.jl/blob/2d078e86489232f77af3696f0ea6d0e34016e7b0/test/support/util.jl
# Copyright (c) 2015: Spencer Russell. Released under the MIT "Expat" License.
mutable struct DummySampleSink{T} <: SampleSink
    samplerate::Float64
    buf::Array{T, 2}
end

DummySampleSink(eltype, samplerate::Number, channels::Int) =
    DummySampleSink{eltype}(samplerate, Array{eltype}(undef, 0, channels))
function DummySampleSink(eltype, samplerate::Union{typeof(1u"Hz"), typeof(1u"kHz"),
                                                   typeof(1.0u"Hz"), typeof(1.0u"kHz")},
                         nchannels::Int)
    samplerate = ustrip(uconvert(u"Hz", samplerate))
    DummySampleSink(eltype, samplerate, nchannels)
end

samplerate(sink::DummySampleSink) = sink.samplerate
nchannels(sink::DummySampleSink) = size(sink.buf, 2)
Base.eltype(sink::DummySampleSink{T}) where T = T

function SampledSignals.unsafe_write(sink::DummySampleSink, buf::Array,
                                     frameoffset, framecount)
    eltype(buf) == eltype(sink) || error("buffer type ($(eltype(buf))) doesn't match sink type ($(eltype(sink)))")
    nchannels(buf) == nchannels(sink) || error("buffer channel count ($(nchannels(buf))) doesn't match sink channel count ($(nchannels(sink)))")

    sink.buf = vcat(sink.buf, view(buf, (1:framecount) .+ frameoffset, :))

    framecount
end

# ######################
# Plotting
# ######################

PlotSpectroTemporal(x::AuditoryStimuli.DummySampleSink; kwargs...) = PlotSpectroTemporal(x.buf, x.samplerate; kwargs...)

function plot(x::AuditoryStimuli.DummySampleSink;
                xlab::String = "Time (s)",
                ylab::String = "Amplitude",
                kwargs...)

    t = 1:size(x.buf, 1)
    t = t ./ x.samplerate

    plot(t, x.buf, xlab = xlab, ylab = ylab; kwargs...)
end


@cache struct SSPRK22Cache{uType,rateType,StageLimiter,StepLimiter} <: OrdinaryDiffEqMutableCache
  u::uType
  uprev::uType
  k::rateType
  fsalfirst::rateType
  stage_limiter!::StageLimiter
  step_limiter!::StepLimiter
end

struct SSPRK22ConstantCache <: OrdinaryDiffEqConstantCache end

function alg_cache(alg::SSPRK22,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{true}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  k = zero(rate_prototype)
  if calck
    fsalfirst = similar(k)
  else
    fsalfirst = k
  end
  SSPRK22Cache(u,uprev,k,fsalfirst,alg.stage_limiter!,alg.step_limiter!)
end

alg_cache(alg::SSPRK22,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{false}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits} = SSPRK22ConstantCache()

@cache struct SSPRK33Cache{uType,rateType,StageLimiter,StepLimiter} <: OrdinaryDiffEqMutableCache
  u::uType
  uprev::uType
  k::rateType
  fsalfirst::rateType
  stage_limiter!::StageLimiter
  step_limiter!::StepLimiter
end

struct SSPRK33ConstantCache <: OrdinaryDiffEqConstantCache end

function alg_cache(alg::SSPRK33,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{true}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  k = zero(rate_prototype)
  if calck
    fsalfirst = similar(k)
  else
    fsalfirst = k
  end
  SSPRK33Cache(u,uprev,k,fsalfirst,alg.stage_limiter!,alg.step_limiter!)
end

alg_cache(alg::SSPRK33,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{false}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits} = SSPRK33ConstantCache()

@cache struct KYKSSPRK42Cache{uType,rateType,TabType} <: OrdinaryDiffEqMutableCache
   u::uType
   uprev::uType
   k::rateType
   tmp::uType
   fsalfirst::rateType
   tab::TabType
 end

struct KYKSSPRK42ConstantCache{T, T2} <: OrdinaryDiffEqConstantCache
  α20::T
  α21::T
  α30::T
  α32::T
  α40::T
  α43::T
  β10::T
  β21::T
  β30::T
  β32::T
  β40::T
  β43::T
  c1::T2
  c2::T2
  c3::T2
end

function KYKSSPRK42ConstantCache(T, T2)
  α20 = T(0.394806441339829)
  α21 = T(0.605193558660171)
  α30 = T(0.002797307087390)
  α32 = T(0.997202692912610)
  α40 = T(0.252860909354373)
  α43 = T(0.747139090645627)
  β10 = T(0.406584463657504)
  β21 = T(0.246062298456822)
  β30 = T(0.013637216641451)
  β32 = T(0.405447122055692)
  β40 = T(0.016453567333598)
  β43 = T(0.303775146447707)
  c1 = T2(0.406584463657504)
  c2 = T2(0.4921245969136438)
  c3 = T2(0.9098323119879613)
  KYKSSPRK42ConstantCache(α20, α21, α30, α32, α40, α43, β10, β21, β30, β32, β40, β43, c1, c2, c3)
end

function alg_cache(alg::KYKSSPRK42,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{true}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  tmp = zero(u)
  k = zero(rate_prototype)
  fsalfirst = zero(rate_prototype)
  tab = KYKSSPRK42ConstantCache(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits))
  KYKSSPRK42Cache(u,uprev,k,tmp, fsalfirst,tab)
end

function alg_cache(alg::KYKSSPRK42,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{false}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  KYKSSPRK42ConstantCache(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits))
end

@cache struct SSPRK53Cache{uType,rateType,StageLimiter,StepLimiter,TabType} <: OrdinaryDiffEqMutableCache
  u::uType
  uprev::uType
  k::rateType
  fsalfirst::rateType
  tmp::uType
  stage_limiter!::StageLimiter
  step_limiter!::StepLimiter
  tab::TabType
end

struct SSPRK53ConstantCache{T,T2} <: OrdinaryDiffEqConstantCache
  α30::T
  α32::T
  α40::T
  α43::T
  α52::T
  α54::T
  β10::T
  β21::T
  β32::T
  β43::T
  β54::T
  c1::T2
  c2::T2
  c3::T2
  c4::T2

  function SSPRK53ConstantCache(T, T2)
    α30 = T(0.355909775063327)
    α32 = T(0.644090224936674)
    α40 = T(0.367933791638137)
    α43 = T(0.632066208361863)
    α52 = T(0.237593836598569)
    α54 = T(0.762406163401431)
    β10 = T(0.377268915331368)
    β21 = T(0.377268915331368)
    β32 = T(0.242995220537396)
    β43 = T(0.238458932846290)
    β54 = T(0.287632146308408)
    c1 = T2(0.377268915331368)
    c2 = T2(0.754537830662736)
    c3 = T2(0.728985661612188)
    c4 = T2(0.699226135931670)

    new{T,T2}(α30, α32, α40, α43, α52, α54, β10, β21, β32, β43, β54, c1, c2, c3, c4)
  end
end

function alg_cache(alg::SSPRK53,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{true}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  tmp = zero(u)
  k = zero(rate_prototype)
  if calck
    fsalfirst = similar(k)
  else
    fsalfirst = k
  end
  tab = SSPRK53ConstantCache(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits))
  SSPRK53Cache(u,uprev,k,fsalfirst,tmp,alg.stage_limiter!,alg.step_limiter!,tab)
end

function alg_cache(alg::SSPRK53,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{false}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  SSPRK53ConstantCache(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits))
end

@cache struct SHLDDRK52Cache{uType,rateType,TabType} <: OrdinaryDiffEqMutableCache
  u::uType
  uprev::uType
  k::rateType
  tmp::uType
  fsalfirst::rateType
  tab::TabType
end

struct SHLDDRK52ConstantCache{T1,T2} <: OrdinaryDiffEqConstantCache
  α2::T1
  α3::T1
  α4::T1
  α5::T1
  β1::T1
  β2::T1
  β3::T1
  β4::T1
  β5::T1
  c2::T2
  c3::T2
  c4::T2
  c5::T2
end

function SHLDDRK52ConstantCache(T1,T2)
  α2 = T1(-0.6913065)
  α3 = T1(-2.655155)
  α4 = T1(-0.8147688)
  α5 = T1(-0.6686587)
  β1 = T1(0.1)
  β2 = T1(0.75)
  β3 = T1(0.7)
  β4 = T1(0.479313)
  β5 = T1(0.310392)
  c2 = T2(0.1)
  c3 = T2(0.3315201)
  c4 = T2(0.4577796)
  c5 = T2(0.8666528)
  SHLDDRK52ConstantCache(α2, α3, α4, α5, β1, β2, β3, β4, β5, c2, c3, c4, c5)
end

function alg_cache(alg::SHLDDRK52,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{false}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  SHLDDRK52ConstantCache(constvalue(uBottomEltypeNoUnits),constvalue(tTypeNoUnits))
end

function alg_cache(alg::SHLDDRK52,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{true}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  tmp = zero(u)
  k = zero(rate_prototype)
  fsalfirst = zero(rate_prototype)
  tab = SHLDDRK52ConstantCache(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits))
  SHLDDRK52Cache(u,uprev,k,tmp,fsalfirst,tab)
end

@cache mutable struct SHLDDRK_2NCache{uType,rateType,TabType} <: OrdinaryDiffEqMutableCache
  u::uType
  uprev::uType
  k::rateType
  tmp::uType
  fsalfirst::rateType
  tab::TabType
  step::Int
end

mutable struct SHLDDRK_2NConstantCache{T1,T2} <: OrdinaryDiffEqConstantCache
  α21::T1
  α31::T1
  α41::T1
  α51::T1
  β11::T1
  β21::T1
  β31::T1
  β41::T1
  β51::T1
  c21::T2
  c31::T2
  c41::T2
  c51::T2

  α22::T1
  α32::T1
  α42::T1
  α52::T1
  α62::T1
  β12::T1
  β22::T1
  β32::T1
  β42::T1
  β52::T1
  β62::T1
  c22::T2
  c32::T2
  c42::T2
  c52::T2
  c62::T2

  step::Int
end

function SHLDDRK_2NConstantCache(T1,T2)
  α21 = T1(-0.6051226)
  α31 = T1(-2.0437564)
  α41 = T1(-0.7406999)
  α51 = T1(-4.4231765)
  β11 = T1(0.2687454)
  β21 = T1(0.8014706)
  β31 = T1(0.5051570)
  β41 = T1(0.5623568)
  β51 = T1(0.0590065)
  c21 = T2(0.2687454)
  c31 = T2(0.5852280)
  c41 = T2(0.6827066)
  c51 = T2(1.1646854)

  α22 = T1(-0.4412737)
  α32 = T1(-1.0739820)
  α42 = T1(-1.7063570)
  α52 = T1(-2.7979293)
  α62 = T1(-4.0913537)
  β12 = T1(0.1158488)
  β22 = T1(0.3728769)
  β32 = T1(0.7379536)
  β42 = T1(0.5798110)
  β52 = T1(1.0312849)
  β62 = T1(0.15)
  c22 = T2(0.1158485)
  c32 = T2(0.3241850)
  c42 = T2(0.6193208)
  c52 = T2(0.8034472)
  c62 = T2(0.9184166)
  SHLDDRK_2NConstantCache(α21, α31, α41, α51, β11, β21, β31, β41, β51, c21, c31, c41, c51, α22, α32, α42, α52, α62, β12, β22, β32, β42, β52, β62, c22, c32, c42, c52, c62, 1)
end

function alg_cache(alg::SHLDDRK_2N,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{false}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  SHLDDRK_2NConstantCache(constvalue(uBottomEltypeNoUnits),constvalue(tTypeNoUnits))
end

function alg_cache(alg::SHLDDRK_2N,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{true}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  tmp = zero(u)
  k = zero(rate_prototype)
  fsalfirst = zero(rate_prototype)
  tab = SHLDDRK_2NConstantCache(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits))
  SHLDDRK_2NCache(u,uprev,k,tmp,fsalfirst,tab,1)
end

@cache struct SSPRK53_2N1Cache{uType,rateType,StageLimiter,StepLimiter,TabType} <: OrdinaryDiffEqMutableCache
  u::uType
  uprev::uType
  k::rateType
  fsalfirst::rateType
  stage_limiter!::StageLimiter
  step_limiter!::StepLimiter
  tab::TabType
end

struct SSPRK53_2N1ConstantCache{T,T2} <: OrdinaryDiffEqConstantCache
  α40::T
  α43::T
  β10::T
  β21::T
  β32::T
  β43::T
  β54::T
  c1::T2
  c2::T2
  c3::T2
  c4::T2

  function SSPRK53_2N1ConstantCache(T, T2)
    α40 = T(0.571403511494104)
    α43 = T(0.428596488505896)
    β10 = T(0.443568244942995)
    β21 = T(0.291111420073766)
    β32 = T(0.270612601278217)
    β43 = T(0.110577759392786)
    β54 = T(0.458557505351052)
    c1 = T2(0.443568244942995)
    c2 = T2(0.734679665016762)
    c3 = T2(1.005292266294979)
    c4 = T2(0.541442494648948)

    new{T,T2}(α40, α43,β10, β21, β32, β43, β54, c1, c2, c3, c4)
  end
end

function alg_cache(alg::SSPRK53_2N1,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{true}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  k = zero(rate_prototype)
  if calck
    fsalfirst = similar(k)
  else
    fsalfirst = k
  end
  tab = SSPRK53_2N1ConstantCache(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits))
  SSPRK53_2N1Cache(u,uprev,k,fsalfirst,alg.stage_limiter!,alg.step_limiter!,tab)
end

function alg_cache(alg::SSPRK53_2N1,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{false}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  SSPRK53_2N1ConstantCache(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits))
end

@cache struct SSPRK53_2N2Cache{uType,rateType,StageLimiter,StepLimiter,TabType} <: OrdinaryDiffEqMutableCache
  u::uType
  uprev::uType
  k::rateType
  fsalfirst::rateType
  stage_limiter!::StageLimiter
  step_limiter!::StepLimiter
  tab::TabType
end

struct SSPRK53_2N2ConstantCache{T,T2} <: OrdinaryDiffEqConstantCache
  α30::T
  α32::T
  α50::T
  α54::T
  β10::T
  β21::T
  β32::T
  β43::T
  β54::T
  c1::T2
  c2::T2
  c3::T2
  c4::T2

  function SSPRK53_2N2ConstantCache(T, T2)
    α30 = T(0.682342861037239)
    α32 = T(0.317657138962761)
    α50 = T(0.045230974482400)
    α54 = T(0.954769025517600)
    β10 = T(0.465388589249323)
    β21 = T(0.465388589249323)
    β32 = T(0.124745797313998)
    β43 = T(0.465388589249323)
    β54 = T(0.154263303748666)
    c1 = T2(0.465388589249323)
    c2 = T2(0.930777178498646)
    c3 = T2(0.420413812847710)
    c4 = T2(0.885802402097033)

    new{T,T2}(α30, α32,α50,α54,β10, β21, β32, β43, β54, c1, c2, c3, c4)
  end
end

function alg_cache(alg::SSPRK53_2N2,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{true}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  k = zero(rate_prototype)
  if calck
    fsalfirst = similar(k)
  else
    fsalfirst = k
  end
  tab = SSPRK53_2N2ConstantCache(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits))
  SSPRK53_2N2Cache(u,uprev,k,fsalfirst,alg.stage_limiter!,alg.step_limiter!,tab)
end

function alg_cache(alg::SSPRK53_2N2,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{false}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  SSPRK53_2N2ConstantCache(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits))
end

@cache struct SSPRK53_HCache{uType,rateType,StageLimiter,StepLimiter,TabType} <: OrdinaryDiffEqMutableCache
  u::uType
  uprev::uType
  k::rateType
  fsalfirst::rateType
  tmp::uType
  stage_limiter!::StageLimiter
  step_limiter!::StepLimiter
  tab::TabType
end

struct SSPRK53_HConstantCache{T,T2} <: OrdinaryDiffEqConstantCache
  α30::T
  α32::T
  α40::T
  α41::T
  α43::T
  β10::T
  β21::T
  β32::T
  β43::T
  β54::T
  c1::T2
  c2::T2
  c3::T2
  c4::T2

  function SSPRK53_HConstantCache(T,T2)
    α30 = T(0.308684154602513)
    α32 = T(0.691315845397487)
    α40 = T(0.280514990468574)
    α41 = T(0.270513101776498)
    α43 = T(0.448971907754928)
    β10 = T(0.377268915331368)
    β21 = T(0.377268915331368)
    β32 = T(0.260811979144498)
    β43 = T(0.169383144652957)
    β54 = T(0.377268915331368)
    c1 = T2(0.377268915331368)
    c2 = T2(0.754537830662737)
    c3 = T2(0.782435937433493)
    c4 = T2(0.622731084668631)

    new{T,T2}(α30, α32, α40, α41, α43, β10, β21, β32, β43, β54, c1, c2, c3, c4)
  end
end

function alg_cache(alg::SSPRK53_H,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{true}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  tmp = zero(u)
  k = zero(rate_prototype)
  if calck
    fsalfirst = similar(k)
  else
    fsalfirst = k
  end
  tab = SSPRK53_HConstantCache(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits))
  SSPRK53_HCache(u,uprev,k,fsalfirst,tmp,alg.stage_limiter!,alg.step_limiter!,tab)
end

function alg_cache(alg::SSPRK53_H,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{false}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  SSPRK53_HConstantCache(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits))
end

@cache struct SSPRK63Cache{uType,rateType,StageLimiter,StepLimiter,TabType} <: OrdinaryDiffEqMutableCache
  u::uType
  uprev::uType
  k::rateType
  fsalfirst::rateType
  tmp::uType
  u₂::uType
  stage_limiter!::StageLimiter
  step_limiter!::StepLimiter
  tab::TabType
end

struct SSPRK63ConstantCache{T,T2} <: OrdinaryDiffEqConstantCache
  α40::T
  α41::T
  α43::T
  α62::T
  α65::T
  β10::T
  β21::T
  β32::T
  β43::T
  β54::T
  β65::T
  c1::T2
  c2::T2
  c3::T2
  c4::T2
  c5::T2

  function SSPRK63ConstantCache(T, T2)
    α40 = T(0.476769811285196)
    α41 = T(0.098511733286064)
    α43 = T(0.424718455428740)
    α62 = T(0.155221702560091)
    α65 = T(0.844778297439909)
    β10 = T(0.284220721334261)
    β21 = T(0.284220721334261)
    β32 = T(0.284220721334261)
    β43 = T(0.120713785765930)
    β54 = T(0.284220721334261)
    β65 = T(0.240103497065900)
    c1 = T2(0.284220721334261)
    c2 = T2(0.568441442668522)
    c3 = T2(0.852662164002783)
    c4 = T2(0.510854218958172)
    c5 = T2(0.795074940292433)

    new{T,T2}(α40, α41, α43, α62, α65, β10, β21, β32, β43, β54, β65, c1, c2, c3, c4, c5)
  end
end

function alg_cache(alg::SSPRK63,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{true}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  tmp = zero(u)
  u₂ = zero(u)
  k = zero(rate_prototype)
  if calck
    fsalfirst = similar(k)
  else
    fsalfirst = k
  end
  tab = SSPRK63ConstantCache(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits))
  SSPRK63Cache(u,uprev,k,fsalfirst,tmp,u₂,alg.stage_limiter!,alg.step_limiter!,tab)
end

function alg_cache(alg::SSPRK63,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{false}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  SSPRK63ConstantCache(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits))
end

@cache struct SSPRK73Cache{uType,rateType,StageLimiter,StepLimiter,TabType} <: OrdinaryDiffEqMutableCache
  u::uType
  uprev::uType
  k::rateType
  fsalfirst::rateType
  tmp::uType
  u₁::uType
  stage_limiter!::StageLimiter
  step_limiter!::StepLimiter
  tab::TabType
end

struct SSPRK73ConstantCache{T,T2} <: OrdinaryDiffEqConstantCache
  α40::T
  α43::T
  α50::T
  α51::T
  α54::T
  α73::T
  α76::T
  β10::T
  β21::T
  β32::T
  β43::T
  β54::T
  β65::T
  β76::T
  c1::T2
  c2::T2
  c3::T2
  c4::T2
  c5::T2
  c6::T2

  function SSPRK73ConstantCache(T, T2)
    α40 = T(0.184962588071072)
    α43 = T(0.815037411928928)
    α50 = T(0.180718656570380)
    α51 = T(0.314831034403793)
    α54 = T(0.504450309025826)
    α73 = T(0.120199000000000)
    α76 = T(0.879801000000000)
    β10 = T(0.233213863663009)
    β21 = T(0.233213863663009)
    β32 = T(0.233213863663009)
    β43 = T(0.190078023865845)
    β54 = T(0.117644805593912)
    β65 = T(0.233213863663009)
    β76 = T(0.205181790464579)
    c1 = T2(0.233213863663009)
    c2 = T2(0.466427727326018)
    c3 = T2(0.699641590989027)
    c4 = T2(0.760312095463379)
    c5 = T2(0.574607439040817)
    c6 = T2(0.807821302703826)

    new{T,T2}(α40, α43, α50, α51, α54, α73, α76, β10, β21, β32, β43, β54, β65, β76, c1, c2, c3, c4, c5, c6)
  end
end

function alg_cache(alg::SSPRK73,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{true}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  tmp = zero(u)
  u₁ = zero(u)
  k = zero(rate_prototype)
  if calck
    fsalfirst = similar(k)
  else
    fsalfirst = k
  end
  tab = SSPRK73ConstantCache(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits))
  SSPRK73Cache(u,uprev,k,fsalfirst,tmp,u₁,alg.stage_limiter!,alg.step_limiter!,tab)
end

function alg_cache(alg::SSPRK73,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{false}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  SSPRK73ConstantCache(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits))
end

@cache struct SSPRK83Cache{uType,rateType,StageLimiter,StepLimiter,TabType} <: OrdinaryDiffEqMutableCache
  u::uType
  uprev::uType
  k::rateType
  fsalfirst::rateType
  tmp::uType
  u₂::uType
  u₃::uType
  stage_limiter!::StageLimiter
  step_limiter!::StepLimiter
  tab::TabType
end

struct SSPRK83ConstantCache{T,T2} <: OrdinaryDiffEqConstantCache
  α50::T
  α51::T
  α54::T
  α61::T
  α65::T
  α72::T
  α73::T
  α76::T
  β10::T
  β21::T
  β32::T
  β43::T
  β54::T
  β65::T
  β76::T
  β87::T
  c1::T2
  c2::T2
  c3::T2
  c4::T2
  c5::T2
  c6::T2
  c7::T2

  function SSPRK83ConstantCache(T, T2)
    α50 = T(0.421366967085359)
    α51 = T(0.005949401107575)
    α54 = T(0.572683631807067)
    α61 = T(0.004254010666365)
    α65 = T(0.995745989333635)
    α72 = T(0.104380143093325)
    α73 = T(0.243265240906726)
    α76 = T(0.652354615999950)
    β10 = T(0.195804015330143)
    β21 = T(0.195804015330143)
    β32 = T(0.195804015330143)
    β43 = T(0.195804015330143)
    β54 = T(0.112133754621673)
    β65 = T(0.194971062960412)
    β76 = T(0.127733653231944)
    β87 = T(0.195804015330143)
    c1 = T2(0.195804015330143)
    c2 = T2(0.391608030660286)
    c3 = T2(0.587412045990429)
    c4 = T2(0.783216061320572)
    c5 = T2(0.561833689734037)
    c6 = T2(0.755247658555329)
    c7 = T2(0.804195984669857)

    new{T,T2}(α50, α51, α54, α61, α65, α72, α73, α76, β10, β21, β32, β43, β54, β65, β76, β87, c1, c2, c3, c4, c5, c6, c7)
  end
end

function alg_cache(alg::SSPRK83,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{true}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  tmp = zero(u)
  u₂ = zero(u)
  u₃ = zero(u)
  k = zero(rate_prototype)
  if calck
    fsalfirst = similar(k)
  else
    fsalfirst = k
  end
  tab = SSPRK83ConstantCache(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits))
  SSPRK83Cache(u,uprev,k,fsalfirst,tmp,u₂,u₃,alg.stage_limiter!,alg.step_limiter!,tab)
end

function alg_cache(alg::SSPRK83,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{false}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  SSPRK83ConstantCache(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits))
end

@cache struct SSPRK43Cache{uType,rateType,uNoUnitsType,StageLimiter,StepLimiter,TabType} <: OrdinaryDiffEqMutableCache
  u::uType
  uprev::uType
  k::rateType
  fsalfirst::rateType
  utilde::uType
  atmp::uNoUnitsType
  stage_limiter!::StageLimiter
  step_limiter!::StepLimiter
  tab::TabType
end

struct SSPRK43ConstantCache{T,T2} <: OrdinaryDiffEqConstantCache
  one_third_u::T
  two_thirds_u::T
  half_u::T
  half_t::T2

  function SSPRK43ConstantCache(T, T2)
    one_third_u = inv(T(3))
    two_thirds_u = 2 * one_third_u
    half_u = T(0.5)
    half_t = T2(0.5)

    new{T,T2}(one_third_u, two_thirds_u, half_u, half_t)
  end
end

function alg_cache(alg::SSPRK43,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{true}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  k = zero(rate_prototype)
  if calck
    fsalfirst = similar(k)
  else
    fsalfirst = k
  end
  utilde = zero(u)
  atmp = similar(u,uEltypeNoUnits)
  tab = SSPRK43ConstantCache(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits))
  SSPRK43Cache(u,uprev,k,fsalfirst,utilde,atmp,alg.stage_limiter!,alg.step_limiter!,tab)
end

function alg_cache(alg::SSPRK43,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{false}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  SSPRK43ConstantCache(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits))
end

@cache struct SSPRK432Cache{uType,rateType,uNoUnitsType,StageLimiter,StepLimiter} <: OrdinaryDiffEqMutableCache
  u::uType
  uprev::uType
  k::rateType
  fsalfirst::rateType
  utilde::uType
  atmp::uNoUnitsType
  stage_limiter!::StageLimiter
  step_limiter!::StepLimiter
end

struct SSPRK432ConstantCache <: OrdinaryDiffEqConstantCache end

function alg_cache(alg::SSPRK432,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{true}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  k = zero(rate_prototype)
  if calck
    fsalfirst = similar(k)
  else
    fsalfirst = k
  end
  utilde = zero(u)
  atmp = similar(u,uEltypeNoUnits)
  SSPRK432Cache(u,uprev,k,fsalfirst,utilde,atmp,alg.stage_limiter!,alg.step_limiter!)
end

alg_cache(alg::SSPRK432,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{false}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits} = SSPRK432ConstantCache()

@cache mutable struct SSPRKMSVS32Cache{uType,rateType,dtArrayType,dtType,StageLimiter,StepLimiter} <: OrdinaryDiffEqMutableCache
  u::uType
  uprev::uType
  fsalfirst::rateType
  u_2::uType
  u_1::uType
  k::rateType
  tmp::uType
  dts::dtArrayType
  dtf::dtArrayType
  μ::dtType
  v_n::Float64
  stage_limiter!::StageLimiter
  step_limiter!::StepLimiter
  step::Int
end

@cache mutable struct SSPRKMSVS32ConstantCache{uType,dtArrayType,dtType} <: OrdinaryDiffEqConstantCache
  u_2::uType
  u_1::uType
  dts::dtArrayType
  dtf::dtArrayType
  μ::dtType
  v_n::Float64
  step::Int
end

function alg_cache(alg::SSPRKMSVS32,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{true}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  fsalfirst = zero(rate_prototype)
  dts = fill(zero(dt),3)
  dtf = fill(zero(dt),2)
  μ = zero(dt)
  u_2 = zero(u)
  u_1 = zero(u)
  k  = zero(rate_prototype)
  tmp = zero(u)
  SSPRKMSVS32Cache(u,uprev,fsalfirst,u_2,u_1,k,tmp,dts,dtf,μ,0.5,alg.stage_limiter!,alg.step_limiter!,1)
end

function alg_cache(alg::SSPRKMSVS32,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{false}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  dts = fill(zero(dt),3)
  dtf = fill(zero(dt),2)
  μ = zero(dt)
  u_2 = u
  u_1 = u
  SSPRKMSVS32ConstantCache(u_2,u_1,dts,dtf,μ,0.5,1)
end

@cache mutable struct SSPRKMSVS43Cache{uType,rateType,StageLimiter,StepLimiter} <: OrdinaryDiffEqMutableCache
  u::uType
  uprev::uType
  fsalfirst::rateType
  u_3::uType
  u_2::uType
  u_1::uType
  k::rateType
  k1::rateType
  k2::rateType
  k3::rateType
  tmp::uType
  stage_limiter!::StageLimiter
  step_limiter!::StepLimiter
  step::Int
end

@cache mutable struct SSPRKMSVS43ConstantCache{uType,rateType} <: OrdinaryDiffEqConstantCache
  u_3::uType
  u_2::uType
  u_1::uType
  k1::rateType
  k2::rateType
  k3::rateType
  step::Int
end

function alg_cache(alg::SSPRKMSVS43,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{true}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  fsalfirst = zero(rate_prototype)
  u_3 = zero(u)
  u_2 = zero(u)
  u_1 = zero(u)
  k  = zero(rate_prototype)
  k1  = zero(rate_prototype)
  k2  = zero(rate_prototype)
  k3  = zero(rate_prototype)
  tmp = zero(u)
  SSPRKMSVS43Cache(u,uprev,fsalfirst,u_3,u_2,u_1,k,k1,k2,k3,tmp,alg.stage_limiter!,alg.step_limiter!,1)
end

function alg_cache(alg::SSPRKMSVS43,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{false}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  u_3 = u
  u_2 = u
  u_1 = u
  k1  = rate_prototype
  k2  = rate_prototype
  k3  = rate_prototype
  SSPRKMSVS43ConstantCache(u_3,u_2,u_1,k1,k2,k3,1)
end

@cache struct SSPRK932Cache{uType,rateType,uNoUnitsType,StageLimiter,StepLimiter} <: OrdinaryDiffEqMutableCache
  u::uType
  uprev::uType
  k::rateType
  fsalfirst::rateType
  utilde::uType
  atmp::uNoUnitsType
  stage_limiter!::StageLimiter
  step_limiter!::StepLimiter
end

struct SSPRK932ConstantCache <: OrdinaryDiffEqConstantCache end

function alg_cache(alg::SSPRK932,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{true}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  k = zero(rate_prototype)
  if calck
    fsalfirst = similar(k)
  else
    fsalfirst = k
  end
  utilde = zero(u)
  atmp = similar(u,uEltypeNoUnits)
  SSPRK932Cache(u,uprev,k,fsalfirst,utilde,atmp,alg.stage_limiter!,alg.step_limiter!)
end

alg_cache(alg::SSPRK932,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{false}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits} = SSPRK932ConstantCache()

@cache struct SSPRK54Cache{uType,rateType,StageLimiter,StepLimiter,TabType} <: OrdinaryDiffEqMutableCache
  u::uType
  uprev::uType
  k::rateType
  fsalfirst::rateType
  k₃::rateType
  u₂::uType
  u₃::uType
  tmp::uType # should be u₄, but tmp is needed for callbacks
  stage_limiter!::StageLimiter
  step_limiter!::StepLimiter
  tab::TabType
end

struct SSPRK54ConstantCache{T,T2} <: OrdinaryDiffEqConstantCache
  β10::T
  α20::T
  α21::T
  β21::T
  α30::T
  α32::T
  β32::T
  α40::T
  α43::T
  β43::T
  α52::T
  α53::T
  β53::T
  α54::T
  β54::T
  c1::T2
  c2::T2
  c3::T2
  c4::T2

  function SSPRK54ConstantCache(T, T2)
    β10 = T(0.391752226571890)
    α20 = T(0.444370493651235)
    α21 = T(0.555629506348765)
    β21 = T(0.368410593050371)
    α30 = T(0.620101851488403)
    α32 = T(0.379898148511597)
    β32 = T(0.251891774271694)
    α40 = T(0.178079954393132)
    α43 = T(0.821920045606868)
    β43 = T(0.544974750228521)
    α52 = T(0.517231671970585)
    α53 = T(0.096059710526147)
    β53 = T(0.063692468666290)
    α54 = T(0.386708617503269)
    β54 = T(0.226007483236906)
    c1 = T2(0.391752226571890)
    c2 = T2(0.586079689311540)
    c3 = T2(0.474542363121400)
    c4 = T2(0.935010630967653)

    new{T,T2}(β10, α20, α21, β21, α30, α32, β32, α40, α43, β43, α52, α53, β53, α54, β54, c1, c2, c3, c4)
  end
end

function alg_cache(alg::SSPRK54,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{true}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  u₂ = zero(u)
  u₃ = zero(u)
  tmp = zero(u)
  k = zero(rate_prototype)
  if calck
    fsalfirst = similar(k)
  else
    fsalfirst = k
  end
  k₃ = zero(rate_prototype)
  tab = SSPRK54ConstantCache(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits))
  SSPRK54Cache(u,uprev,k,fsalfirst,k₃,u₂,u₃,tmp,alg.stage_limiter!,alg.step_limiter!,tab)
end

function alg_cache(alg::SSPRK54,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{false}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  SSPRK54ConstantCache(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits))
end

@cache struct SSPRK104Cache{uType,rateType,StageLimiter,StepLimiter} <: OrdinaryDiffEqMutableCache
  u::uType
  uprev::uType
  k::rateType
  fsalfirst::rateType
  k₄::rateType
  tmp::uType
  stage_limiter!::StageLimiter
  step_limiter!::StepLimiter
end

struct SSPRK104ConstantCache <: OrdinaryDiffEqConstantCache end

function alg_cache(alg::SSPRK104,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{true}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits}
  tmp = zero(u)
  k = zero(rate_prototype)
  if calck
    fsalfirst = similar(k)
  else
    fsalfirst = k
  end
  k₄ = zero(rate_prototype)
  SSPRK104Cache(u,uprev,k,fsalfirst,k₄,tmp,alg.stage_limiter!,alg.step_limiter!)
end

alg_cache(alg::SSPRK104,u,rate_prototype,::Type{uEltypeNoUnits},::Type{uBottomEltypeNoUnits},::Type{tTypeNoUnits},uprev,uprev2,f,t,dt,reltol,p,calck,::Val{false}) where {uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits} = SSPRK104ConstantCache()


using Base.Test
include("reduce.jl")

dev = CuDevice(0)
if capability(dev) < v"3.0"
    warn("this example requires a newer GPU")
    exit(0)
end

len = 10^7
input = ones(Int32, len)

# CPU
cpu_val = reduce(+, input)

# CUDAnative
let
    ctx = CuContext(dev)
    gpu_input = CuArray(input)
    gpu_output = similar(gpu_input)
    gpu_reduce(+, gpu_input, gpu_output)
    gpu_val = Array(gpu_output)[1]
    destroy(ctx)
    @assert cpu_val == gpu_val
end


# BLAS/LAPACK dependency for HSL.jl

libblas = library_dependency("libblas")
liblapack = library_dependency("liblapack")

@static if Sys.isapple()
  provides(Homebrew.HB, "homebrew/core/openblas", libblas, os=:Darwin)
  provides(Homebrew.HB, "homebrew/core/openblas", liblapack, os=:Darwin)
end

provides(AptGet, "libblas-dev", libblas, os=:Linux)
provides(AptGet, "liblapack-dev", liblapack, os=:Linux)

# try to guess what BinDeps installed
blas_lib = "-lblas"
lapack_lib = "-llapack"
@static if Sys.isapple()
  hb_libopenblas_dir = joinpath(Homebrew.prefix("openblas"), "lib")
  if isdir(hb_libopenblas_dir)
    blas_lib = "-L$hb_libopenblas_dir -lopenblas"
    lapack_lib = "-L$hb_libopenblas_dir -lopenblas"
  end
end


push!(LOAD_PATH, "./core/")

using core

cfgfile = ""
# cfgfile = "../input/markus-paper.cfg"
# cfgfile = "../input/gideon-master-thesis-isotropic.cfg"
# cfgfile = "../input/daniel-master-thesis-isotropic.cfg"

p_state = State_Setup(cfgfile)

Simulation_PlayPause(p_state, "LLG", "SIB", 100, 100)


# By default, an AbstractOperator is assumed to be time-independent.
midpoint!(::Union{AbstractOperator,Nothing}, t, Δt) = nothing
integrate!(::Union{AbstractOperator,Nothing}, t, Δt) = nothing


include("./server.jl")

ENV["JULIA_MARGO_LOAD_PYPLOT"] = "no thank you"
import ClimateMARGO
using ClimateMARGO.Models
using ClimateMARGO.Optimization
using ClimateMARGO.Diagnostics

# some silly code to make our custom types work with MsgPack (JSON alternative)
import MsgPack
MsgPack.msgpack_type(::Type{ClimateModel}) = MsgPack.StructType()
MsgPack.msgpack_type(::Type{ClimateModelParameters}) = MsgPack.StructType()
MsgPack.msgpack_type(::Type{Domain}) = MsgPack.StructType()
MsgPack.msgpack_type(::Type{Economics}) = MsgPack.StructType()
MsgPack.msgpack_type(::Type{Physics}) = MsgPack.StructType()
MsgPack.msgpack_type(::Type{Controls}) = MsgPack.StructType()

function setfieldconvert!(value, name::Symbol, x)
    setfield!(value, name, convert(typeof(getfield(value, name)), x))
end
# the main margo function
@expose function opt_controls_temp(;dt=20, opt_parameters, economics=Dict(), physics=Dict())
    model_parameters = deepcopy(ClimateMARGO.IO.included_configurations["default"])::ClimateModelParameters
    model_parameters.domain = Domain(Float64(dt), 2020.0, 2200.0)
    model_parameters.economics.baseline_emissions = ramp_emissions(model_parameters.domain)
    model_parameters.economics.extra_CO₂ = zeros(size(model_parameters.economics.baseline_emissions))

    for (k, v) in economics
        setfieldconvert!(model_parameters.economics, Symbol(k), v)
    end
    for (k, v) in physics
        setfieldconvert!(model_parameters.physics, Symbol(k), v)
    end

    model = ClimateModel(model_parameters)

    parsed = Dict((Symbol(k) => v) for (k, v) in opt_parameters)
    model_optimizer = optimize_controls!(model; parsed..., print_raw_status=false)
    return Dict(
        :model_parameters => model_parameters,
        :status => ClimateMARGO.Optimization.JuMP.termination_status(model_optimizer) |> string,
        model_results(model)...
    )
end

@expose function forward_controls_temp(;dt=20, controls=Dict(), economics=Dict(), physics=Dict())
    model_parameters = deepcopy(ClimateMARGO.IO.included_configurations["default"])::ClimateModelParameters
    model_parameters.domain = Domain(Float64(dt), 2020.0, 2200.0)
    model_parameters.economics.baseline_emissions = ramp_emissions(model_parameters.domain)
    model_parameters.economics.extra_CO₂ = zeros(size(model_parameters.economics.baseline_emissions))

    for (k, v) in economics
        setfieldconvert!(model_parameters.economics, Symbol(k), v)
    end
    for (k, v) in physics
        setfieldconvert!(model_parameters.physics, Symbol(k), v)
    end

    model = ClimateModel(model_parameters)

    translations = Dict(
        :M => :mitigate,
        :R => :remove,
        :G => :geoeng,
        :A => :adapt,
    )
    for (k, v) in controls
        setfieldconvert!(model.controls, translations[Symbol(k)], v)
    end

    return Dict(
        :model_parameters => model_parameters,
        model_results(model)...
    )
end

model_results(model::ClimateModel) = Dict(
    :controls => model.controls,
    :computed => Dict(
        :temperatures => Dict(
            :baseline => T_adapt(model),
            :M => T_adapt(model; M=true),
            :MR => T_adapt(model; M=true, R=true),
            :MRG => T_adapt(model; M=true, R=true, G=true),
            :MRGA => T_adapt(model; M=true, R=true, G=true, A=true),
        ),
        :emissions => Dict(
            :baseline => effective_emissions(model),
            :M => effective_emissions(model; M=true),
            :MRGA => effective_emissions(model; M=true, R=true),
        ),
        :concentrations => Dict(
            :baseline => c(model),
            :M => c(model; M=true),
            :MRGA => c(model; M=true, R=true),
        ),
        :damages => Dict(
            :baseline => costs_dict(damage(model; discounting=true), model),
            :MRGA => costs_dict(damage(model; M=true, R=true, G=true, A=true, discounting=true), model),
        ),
        :costs => Dict(
            :M => costs_dict(cost(model; M=true, discounting=true), model),
            :R => costs_dict(cost(model; R=true, discounting=true), model),
            :G => costs_dict(cost(model; G=true, discounting=true), model),
            :A => costs_dict(cost(model; A=true, discounting=true), model),
            :MRGA => costs_dict(cost(model; M=true, R=true, G=true, A=true, discounting=true), model),
        ),
    ),
)

function costs_dict(costs, model)
    Dict(
        :discounted => costs,
        :total_discounted => sum(costs .* model.domain.dt),
    )
end

# simple function as baseline for connection speed
@expose function arithmetic(;a, b)
    Dict(
        :sum => a + b,
        :difference => a - b,
    )
end

@expose function randimage(;width, height)
    img = rand(UInt8, width * height * 4)
    Dict(
        :img => img,
    )
end


using DFWannier
using ProfileView
T = Float32
x = WannierModel{T}("/Users/ponet/Documents/Fysica/PhD/GeTe/colin/paperxsf/test1","/Users/ponet/Documents/Fysica/PhD/GeTe/fullrel/GeTe_bands.out",[[Atom(T[0.0,0.0,-0.0239129,-0.155854]...) for i=1:4]...,[Atom(T[0.0,0.0,5.5540692,0.318205]...) for i=1:4]...]);
@time tbbandssoc = calculate_eig_cm_angmom_soc(x,90:0.2:110);
Profile.clear()
using BenchmarkTools
@benchmark tbbandssoc = calculate_eig_cm_angmom_soc(x,90:0.2:110)
@profile tbbandssoc1 = calculate_eig_cm_angmom_soc(x,50:0.2:110);
@time calculate_angmom(x.wfcs[1],x.wfcs[3])
Profile.clear()
Profile.init(1000000000, 0.00100000)
ProfileView.view()
@code_warntype read_xsf_file("/Users/ponet/Documents/Fysica/PhD/GeTe/colin/paperxsf/test1")

struct perfTest
  val::Float64
  function perfTest()
    new(rand(Float64))
  end
end

struct perfTest2
  val::Float64
  t::Bool
  function perfTest2()
    new(rand(Float64),rand(Bool))
  end
end

const test_array = [perfTest() for i=1:10000000]
const test_array2 = [perfTest2() for i=1:10000000]

function bench_test(t)
  out = 0.0
  @inbounds for i=1:10000000
    out += t[i].val
  end
  out
end
# using StaticArrays
function bench_test2(t)
  out = 0.0
  v = Array{perfTest2,1}(12)
  @inbounds for i=1:10:10000000
    unsafe_copy!(v,1,t,i,12)
    @inbounds for j = 1:12
    out += v[j].val
    end
  end
  out
end
function bench_test2(t)
  out = 0.0
  @inbounds for i=1:10000000
    out += t[i].val
  end
  out
end

using BenchmarkTools
@code_native bench_test(test_array)
@code_native bench_test2(test_array2)


const BBox = Rect2D{Float32}

const Optional{T} = Union{Nothing, T}

struct RectSides{T}
    left::T
    right::T
    bottom::T
    top::T
end

abstract type Side end

struct Left <: Side end
struct Right <: Side end
struct Top <: Side end
struct Bottom <: Side end
# for protrusion content:
struct TopLeft <: Side end
struct TopRight <: Side end
struct BottomLeft <: Side end
struct BottomRight <: Side end

struct Inner <: Side end
struct Outer <: Side end

abstract type GridDir end
struct Col <: GridDir end
struct Row <: GridDir end

struct RowCols{T <: Union{Number, Vector{Float64}}}
    lefts::T
    rights::T
    tops::T
    bottoms::T
end

"""
    struct Span

Used to specify space that is occupied in a grid. Like 1:1|1:1 for the first square,
or 2:3|1:4 for a rect over the 2nd and 3rd row and the first four columns.
"""
struct Span
    rows::UnitRange{Int64}
    cols::UnitRange{Int64}
end

"""
    mutable struct GridContent{G, T}

Wraps content elements of a `GridLayout`. It keeps track of the `parent`, the `content` and its position in the grid via `span` and `side`.
"""
mutable struct GridContent{G, T} # G should be GridLayout but can't be used before definition
    parent::Optional{G}
    content::T
    span::Span
    side::Side
    needs_update::Node{Bool}
    protrusions_handle::Optional{Function}
    computedsize_handle::Optional{Function}
end

abstract type AlignMode end

"AlignMode that excludes the protrusions from the bounding box."
struct Inside <: AlignMode end

"AlignMode that includes the protrusions within the bounding box, plus paddings."
struct Outside <: AlignMode
    padding::RectSides{Float32}
end
Outside() = Outside(0f0)
Outside(padding::Real) = Outside(RectSides{Float32}(padding, padding, padding, padding))
Outside(left::Real, right::Real, bottom::Real, top::Real) =
    Outside(RectSides{Float32}(left, right, bottom, top))

"AlignMode that is Inside where padding is Nothing and Outside where it is Real."
struct Mixed <: AlignMode
    padding::RectSides{Union{Nothing, Float32}}
end
function Mixed(; left = nothing, right = nothing, bottom = nothing, top = nothing)
    paddings = map((left, right, bottom, top)) do side
        isnothing(side) ? side : Float32(side)
    end
    Mixed(RectSides{Union{Nothing, Float32}}(paddings...))
end

abstract type ContentSize end
abstract type GapSize <: ContentSize end

"""
    struct Auto <: ContentSize

If used as a `GridLayout`'s row / column size and `trydetermine == true`, signals to the `GridLayout` that the row / column should shrink to match the largest determinable element inside.
If no size of a content element can be determined, the remaining space is split between all `Auto` rows / columns according to their `ratio`.

If used as width / height of a layoutable element and `trydetermine == true`, the element's computed width / height will report the auto width / height if it can be determined.
This enables a parent `GridLayout` to adjust its column / rowsize to the element's width / height.
If `trydetermine == false`, the element's computed width / height will report `nothing` even if an auto width / height can be determined, which will prohibit a parent `GridLayout` from adjusting a row / column to the element's width / height.
This is useful to, e.g., prohibit a `GridLayout` from shrinking a column's width to the width of a super title, even though the title's width can be auto-determined.

The `ratio` is ignored if `Auto` is used as an element size.
"""
struct Auto <: ContentSize
    trydetermine::Bool # false for determinable size content that should be ignored
    ratio::Float64 # float ratio in case it's not determinable

    Auto(trydetermine::Bool = true, ratio::Real = 1.0) = new(trydetermine, ratio)
end
Auto(ratio::Real) = Auto(true, ratio)

struct Fixed <: GapSize
    x::Float64
end
struct Relative <: GapSize
    x::Float64
end
struct Aspect <: ContentSize
    index::Int
    ratio::Float64
end

"""
    mutable struct LayoutNodes{T, G}

`T` is the same type parameter of contained `GridContent`, `G` is `GridLayout` which is defined only after `LayoutNodes`.

A collection of `Node`s and an optional `GridContent` that are needed to interface with the MakieLayout layouting system.

- `suggestedbbox::Node{BBox}`: The bounding box that an element should place itself in. Depending on the element's `width` and `height` attributes, this is not necessarily equal to the computedbbox.
- `protrusions::Node{RectSides{Float32}}`: The sizes of content "sticking out" of the main element into the `GridLayout` gaps.
- `computedsize::Node{NTuple{2, Optional{Float32}}}`: The width and height that the element computes for itself if possible (else `nothing`).
- `autosize::Node{NTuple{2, Optional{Float32}}}`: The width and height that the element reports to its parent `GridLayout`. If the element doesn't want to cause the parent to adjust to its size, autosize can hide the computedsize from it by being set to `nothing`.
- `computedbbox::Node{BBox}`: The bounding box that the element computes for itself after it has received a suggestedbbox.
- `gridcontent::Optional{GridContent{G, T}}`: A reference of a `GridContent` if the element is currently placed in a `GridLayout`. This can be used to retrieve the parent layout, remove the element from it or change its position, and assign it to a different layout.
"""
mutable struct LayoutNodes{T, G} # G again GridLayout
    suggestedbbox::Node{BBox}
    protrusions::Node{RectSides{Float32}}
    computedsize::Node{NTuple{2, Optional{Float32}}}
    autosize::Node{NTuple{2, Optional{Float32}}}
    computedbbox::Node{BBox}
    gridcontent::Optional{GridContent{G, T}} # the connecting link to the gridlayout
end

mutable struct GridLayout
    content::Vector{GridContent}
    nrows::Int
    ncols::Int
    rowsizes::Vector{ContentSize}
    colsizes::Vector{ContentSize}
    addedrowgaps::Vector{GapSize}
    addedcolgaps::Vector{GapSize}
    alignmode::AlignMode
    equalprotrusiongaps::Tuple{Bool, Bool}
    needs_update::Node{Bool}
    block_updates::Bool
    layoutnodes::LayoutNodes
    attributes::Attributes
    parentscene::Optional{AbstractPlotting.Scene}
    _update_func_handle::Optional{Function} # stores a reference to the result of on(obs)

    function GridLayout(
        content, nrows, ncols, rowsizes, colsizes,
        addedrowgaps, addedcolgaps, alignmode, equalprotrusiongaps, needs_update,
        layoutnodes, attributes, parentscene)

        gl = new(content, nrows, ncols, rowsizes, colsizes,
            addedrowgaps, addedcolgaps, alignmode, equalprotrusiongaps,
            needs_update, false, layoutnodes, attributes, parentscene, nothing)

        validategridlayout(gl)

        # attach_parent!(gl, parent)

        # on(needs_update) do update
        #     request_update(gl)
        # end

        gl
    end
end

struct AxisAspect
    aspect::Float32
end

struct DataAspect end

abstract type Ticks end

struct AutoLinearTicks <: Ticks
    idealtickdistance::Float32
end

struct ManualTicks <: Ticks
    values::Vector{Float32}
    labels::Vector{String}
end

"""
    struct CustomTicks{F1<:Function, F2<:Function} <: Ticks

For the use of custom functions that compute tick values and labels in an `LAxis`.

- `f_tickvalues::F1`: A function that takes minimum_value, maximum_value, and pixelwidth as arguments and returns a `Float` array of tick values.
- `f_ticklabels::F2`: A function that takes in an array of `Float`s and returns an array of `String` labels.
"""
struct CustomTicks{F1<:Function, F2<:Function} <: Ticks
    f_tickvalues::F1
    f_ticklabels::F2
end

mutable struct LineAxis
    parent::Scene
    protrusion::Node{Float32}
    attributes::Attributes
    decorations::Dict{Symbol, Any}
    tickpositions::Node{Vector{Point2f0}}
    tickvalues::Node{Vector{Float32}}
    ticklabels::Node{Vector{String}}
end

abstract type LObject end

mutable struct LAxis <: AbstractPlotting.AbstractScene
    parent::Scene
    scene::Scene
    xaxislinks::Vector{LAxis}
    yaxislinks::Vector{LAxis}
    limits::Node{BBox}
    layoutnodes::LayoutNodes
    needs_update::Node{Bool}
    attributes::Attributes
    block_limit_linking::Node{Bool}
    decorations::Dict{Symbol, Any}
end

mutable struct LColorbar <: LObject
    parent::Scene
    scene::Scene
    layoutnodes::LayoutNodes
    attributes::Attributes
    decorations::Dict{Symbol, Any}
end

mutable struct LText <: LObject
    parent::Scene
    layoutnodes::LayoutNodes
    textobject::AbstractPlotting.Text
    attributes::Attributes
end

mutable struct LRect <: LObject
    parent::Scene
    layoutnodes::LayoutNodes
    rect::AbstractPlotting.Poly
    attributes::Attributes
end

struct LSlider <: LObject
    scene::Scene
    layoutnodes::LayoutNodes
    attributes::Attributes
    decorations::Dict{Symbol, Any}
end

struct LButton <: LObject
    scene::Scene
    layoutnodes::LayoutNodes
    attributes::Attributes
    decorations::Dict{Symbol, Any}
end

struct LToggle <: LObject
    scene::Scene
    layoutnodes::LayoutNodes
    attributes::Attributes
    decorations::Dict{Symbol, Any}
end

abstract type LegendElement end

struct LineElement <: LegendElement
    attributes::Attributes
end

struct MarkerElement <: LegendElement
    attributes::Attributes
end

struct PolyElement <: LegendElement
    attributes::Attributes
end

struct LegendEntry
    elements::Vector{LegendElement}
    attributes::Attributes
end

struct LLegend <: LObject
    scene::Scene
    entries::Node{Vector{LegendEntry}}
    layoutnodes::LayoutNodes
    attributes::Attributes
    decorations::Dict{Symbol, Any}
    entrytexts::Vector{LText}
    entryplots::Vector{Vector{AbstractPlot}}
end

const Indexables = Union{UnitRange, Int, Colon}

struct GridPosition
    layout::GridLayout
    rows::Indexables # this doesn't warrant type parameters I think
    cols::Indexables # as these objects will only be used briefly
end

struct LScene <: AbstractPlotting.AbstractScene
    scene::Scene
    attributes::Attributes
    layoutnodes::MakieLayout.LayoutNodes
end


# using MLDatasets: MNIST
using Base.Iterators
import Pkg; Pkg.add("Augmentor"); using Augmentor

function test_image_loader()
    augm = CropSize(28,28)
    trn, vld = mk_image_minibatch("../data/flowers",
                4; split=true, fr=0.2,
                balanced=true, shuffle=true,
                train=true,
                aug_pipl=augm, pre_proc=preproc_imagenet)

    lab = get_class_labels(trn)

    return size(first(trn)[1]) == (28,28,3,4) &&
           size(lab) == (3,)
end

function test_image_preload()
    tst = mk_image_minibatch("../data/flowers",
                4; split=false, fr=0.2,
                balanced=false, shuffle=false,
                train=false, pre_load=true,
                aug_pipl=nothing, pre_proc=nothing)

    return size(first(tst)) == (256, 256, 3, 4)
end

function test_image2arr()
        img = load("../data/elecat/cat.jpg")
        x = image2array(img)
        return size(x) == (1024, 1001, 3) && eltype(x) == Float32
end

function test_array2image()
        img1 = array2image(rand(256,256,3))
        img2 = array2image(rand(256,256))
        img3 = array2RGB(rand(256,256))

        return eltype(img1) <: RGB &&
               eltype(img2) <: Gray &&
               eltype(img3) <: RGB
end

# imagenet:
#
function test_preproc_imagenet()

    img = rand(32,32,3)
    img = preproc_imagenet(img)
    return size(img) == (32,32,3)
end

function test_in_classes()
    c = get_imagenet_classes()
    return length(c) == 1000
end


using Revise
using westbrick

num_objects = 5
positions = [[i,j] for (i,j) in zip([4.,3.,2.,2.5,0.3],[2.,3.,4.,2.,4.])]
objects = init_environment(num_objects, positions)
obs_dict = visualize_environment!(objects)

bobby = load_robot()
traj, obj_traj = init_visualization()

pose = [positions[1]...,π]
move!(bobby, pose, traj, obj_traj)
pick!(bobby, π, objects[1], traj, obj_traj)
move!(bobby, [-3.,-3., 0.], traj, obj_traj)
place!(bobby, -π, traj, obj_traj)
move!(bobby, [3.,-3., 0.], traj, obj_traj)

pose = [positions[5]...,π]
move!(bobby, pose, traj, obj_traj)
pick!(bobby, π, objects[5], traj, obj_traj)
move!(bobby, [-2.,-3., 0.], traj, obj_traj)
place!(bobby, -π, traj, obj_traj)
move!(bobby, [3.,-3., 0.], traj, obj_traj)

println("visualizing...")
visualize_trajectory!(bobby, traj, obj_traj, obs_dict)


"""
Molecular structure manipulation and analysis
"""
module Dorothy

using LinearAlgebra
using Statistics
using Formats
using FormatStreams

export
		MolecularModelHeader, MolecularModel, MolecularModelView,
		ParticleCollection, Particle, MolecularTrajectory, pbcstrategy,
		pbcstrategy!, SelectionCache, SelectionMode, Selector, Selectors,
		DorothyIO, @DorothyAll

include("utils.jl")
include("properties.jl")
include("headers.jl")
include("graphs.jl")
include("multicolls.jl")
include("geometry.jl")
include("pbc.jl")
include("neighbors.jl")

using .Utils
using .Properties
using .Headers
using .Graphs
using .Multicollections
using .Geometry
using .PBC
using .Neighbors

include("models.jl")
include("atoms.jl")
include("hierarchies.jl")

using .Atoms
using .Hierarchies

include("selections.jl")

using .Selectors

struct DorothyIO <: Formats.FormatHandler end

include("formats/gromos87.jl")
include("formats/pdb.jl")
include("formats/xdr.jl")
include("formats/trr.jl")
include("formats/xtc.jl")
include("formats/ndx.jl")

using .Gromos87
using .PDB
using .TRR
using .XTC
using .NDX

include("ss.jl")
include("topology.jl")

using .SecondaryStructure
using .Topology

macro DorothyAll()
	return :( using LinearAlgebra,
			Statistics,
			Formats,
			FormatCodecs,
			FormatStreams,
			Dorothy,
			Dorothy.Utils,
			Dorothy.Properties,
			Dorothy.Graphs,
			Dorothy.Multicollections,
			Dorothy.Geometry,
			Dorothy.PBC,
			Dorothy.Neighbors,
			Dorothy.Atoms,
			Dorothy.Hierarchies,
			Dorothy.Selectors,
			Dorothy.Gromos87,
			Dorothy.PDB,
			Dorothy.TRR,
			Dorothy.XTC,
			Dorothy.NDX,
			Dorothy.SecondaryStructure,
			Dorothy.Topology )
end

datapath = joinpath(@__DIR__, "..", "data")

function __init__()
	register_gromos87()
	register_pdb()
	register_trr()
	register_xtc()
	register_ndx()
end

end # module


# © 2016 Massachusetts Institute of Technology.  See LICENSE file for details.
#__precompile__(true)
module Seep

using HDF5, JLD, Compat

export ANode, sigm, softmax, gradients, evaluator, BuddyPool, NullPool, instance, evaluate,
  gradient_descent, momentum, adadelta, adam, weight_update, graphviz, connected_nodes,
  load_snapshot, save_snapshot, rmsprop, name!, sigmoid_loss, softplus

if !haskey(ENV, "SEEP_NO_GPU")
  using CUDAdrv, CUBLAS, SHA
  import CUDArt: device, free, CudaArray, to_host, device_synchronize
  #macro llvm(x) esc(x) end
  macro llvm(x) end
  macro cuda(x) esc(x) end
  #include("llvm/LLVM.jl")
  #include("llvm/NVVM.jl")
  #import .LLVM: BuilderRef, ValueRef, ModuleRef, TypeRef
  include("cudahacks.jl")
else
  macro cuda(x) end
  macro cuda_kernel(x...) end
  macro cuda_gsl(x...) end
  macro llvm(x) end
end

macro cuda_text(x) end
macro cuda_include(x) end

include("sources.jl")

@llvm include("llvm/llvm-instance.jl")

include("flows.jl")
export Flow, Linear, RNN, LSTM, GRU, Conv2D, Pool, Dropout, RandomNoise
export get_params, reg_params, scanl, scanl!, in_size, out_size

if isdefined(Seep, :do_forward)
  error("I think you meant do_forward!")
end

end