primitives(vp::AbstractVolumePrimitive) = (vp,)
function primitives(csg::AbstractConstructiveGeometry)
    ps = []
    push!(ps, primitives(csg.a)...)
    push!(ps, primitives(csg.b)...)
    ps
end

@recipe function f(csg::AbstractConstructiveGeometry)
    ps = primitives(csg)
    @series begin
        label --> "CSG"
        if !isClosedPrimitive(ps[1])
            fillcolor := :white
            fillalpha --> 0.2
            if haskey(plotattributes, :seriestype)
                if plotattributes[:seriestype] in [:csg, :wireframe]
                    linewidth := 0.5
                end
            end
        end
        ps[1]
    end
    for i in 2:length(ps)
        @series begin
            label := ""
            if !isClosedPrimitive(ps[i])
                fillcolor := :white
                fillalpha --> 0.2
                if haskey(plotattributes, :seriestype)
                    if plotattributes[:seriestype] in [:csg, :wireframe]
                        linewidth := 0.5
                    end
                end
            end
            ps[i]
        end
    end
end


module CDF

# loads a graph from a IEEE CDF file.
# http://www2.ee.washington.edu/research/pstca/formats/cdf.txt
# http://www2.ee.washington.edu/research/pstca/pf30/ieee30cdf.txt

using LightGraphs
using LightGraphs: AbstractGraphFormat

import LightGraphs: loadgraph, loadgraphs, savegraph

export CDFFormat

struct CDFFormat <: AbstractGraphFormat end

function _loadcdf(io::IO)
    srcs = Vector{Int}()
    dsts = Vector{Int}()
    vertices = Vector{Int}()
    inbusdata = false
    inbranchdata = false
    while !eof(io)
        line = strip(chomp(readline(io)))
        if inbusdata
            if occursin("-999", line)
                inbusdata = false
            else
                v = parse(Int, split(line)[1])
                push!(vertices, v)
            end
        elseif inbranchdata
            if occursin("-999", line)
                inbranchdata = false
            else
                (src_s, dst_s) = split(line)[1:2]
                src = something(findfirst(isequal(parse(Int, src_s)), vertices), 0)
                dst = something(findfirst(isequal(parse(Int, dst_s)), vertices), 0)
                push!(srcs, src)
                push!(dsts, dst)
            end
        else
            inbusdata = startswith(line, "BUS DATA FOLLOWS")
            inbranchdata = startswith(line, "BRANCH DATA FOLLOWS")
        end
    end
    n_v = length(vertices)
    g = LightGraphs.Graph(n_v)
    for p in zip(srcs, dsts)
        add_edge!(g, p)
    end
    return g
end

loadcdf(io::IO, gname::String) = _loadcdf(io)
loadgraph(io::IO, gname::String, ::CDFFormat) = loadcdf(io, gname)
loadgraphs(io::IO, ::CDFFormat) = Dict("graph" => loadcdf(io, "graph"))

end # module


using Test
using Random
using VectorCalculus

Random.seed!(1234)

@testset "VectorCalculus" begin
    using LinearAlgebra
    include("algebraic.jl")
    include("differential.jl")
end


replacements = split("""Al => ThF
Al => ThRnFAr
B => BCa
B => TiB
B => TiRnFAr
Ca => CaCa
Ca => PB
Ca => PRnFAr
Ca => SiRnFYFAr
Ca => SiRnMgAr
Ca => SiTh
F => CaF
F => PMg
F => SiAl
H => CRnAlAr
H => CRnFYFYFAr
H => CRnFYMgAr
H => CRnMgYFAr
H => HCa
H => NRnFYFAr
H => NRnMgAr
H => NTh
H => OB
H => ORnFAr
Mg => BF
Mg => TiMg
N => CRnFAr
N => HSi
O => CRnFYFAr
O => CRnMgAr
O => HP
O => NRnFAr
O => OTi
P => CaP
P => PTi
P => SiRnFAr
Si => CaSi
Th => ThCa
Ti => BP
Ti => TiTi
e => HF
e => NAl
e => OMg""", "\n")

inpt = """ORnPBPMgArCaCaCaSiThCaCaSiThCaCaPBSiRnFArRnFArCaCaSiThCaCaSiThCaCaCaCaCaCaSiRnFYFArSiRnMgArCaSiRnPTiTiBFYPBFArSiRnCaSiRnTiRnFArSiAlArPTiBPTiRnCaSiAlArCaPTiTiBPMgYFArPTiRnFArSiRnCaCaFArRnCaFArCaSiRnSiRnMgArFYCaSiRnMgArCaCaSiThPRnFArPBCaSiRnMgArCaCaSiThCaSiRnTiMgArFArSiThSiThCaCaSiRnMgArCaCaSiRnFArTiBPTiRnCaSiAlArCaPTiRnFArPBPBCaCaSiThCaPBSiThPRnFArSiThCaSiThCaSiThCaPTiBSiRnFYFArCaCaPRnFArPBCaCaPBSiRnTiRnFArCaPRnFArSiRnCaCaCaSiThCaRnCaFArYCaSiRnFArBCaCaCaSiThFArPBFArCaSiRnFArRnCaCaCaFArSiRnFArTiRnPMgArF"""

solutions = Set{ASCIIString}()
push!(solutions, inpt) # to cover the case of no matches. Will be subtracted at the end
subst = r"(\w+) => (\w+)"

#part 1
for line in replacements
	m = match(subst, line)
	for i in 1:length(inpt)
		sol = inpt[1:i-1]*replace(inpt[i:end], m.captures[1], m.captures[2], 1)
		push!(solutions, sol)
	end
end
println(length(solutions)-1) # one is the original molecule, in case of no substitutions

# part 2
# solution from here https://www.reddit.com/r/adventofcode/comments/3xflz8/day_19_solutions/cy4etju
# I can't figure this out ...


# RELOG: Reverse Logistics Optimization
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.

using DataStructures
using JSON
using JSONSchema
using Printf
using Statistics

mutable struct Product
    name::String
    transportation_cost::Vector{Float64}
    transportation_energy::Vector{Float64}
    transportation_emissions::Dict{String,Vector{Float64}}
end

mutable struct CollectionCenter
    index::Int64
    name::String
    latitude::Float64
    longitude::Float64
    product::Product
    amount::Vector{Float64}
end

mutable struct PlantSize
    capacity::Float64
    variable_operating_cost::Vector{Float64}
    fixed_operating_cost::Vector{Float64}
    opening_cost::Vector{Float64}
end

mutable struct Plant
    index::Int64
    plant_name::String
    location_name::String
    input::Product
    output::Dict{Product,Float64}
    latitude::Float64
    longitude::Float64
    disposal_limit::Dict{Product,Vector{Float64}}
    disposal_cost::Dict{Product,Vector{Float64}}
    sizes::Vector{PlantSize}
    energy::Vector{Float64}
    emissions::Dict{String,Vector{Float64}}
    storage_limit::Float64
    storage_cost::Vector{Float64}
end

mutable struct Instance
    time::Int64
    products::Vector{Product}
    collection_centers::Vector{CollectionCenter}
    plants::Vector{Plant}
    building_period::Vector{Int64}
end


using Calmato
using Test

@testset "Calmato.jl" begin
    db1 = search_db("Cu Zn Liang Hsiao 2015")
    print(db1)
    sys = init_system(db1)
    print(sys)
    res = solidify(sys)
    print(res)

    db2 = search_db("Cu H O S P Magnusson")
    db2 = select(db2, "Cu S", *, -1)
    print(db2)

    db3 = merge(db1, db2, 1 => 1, 2 => 4, *)
    print(db3)
    sys = init_system(db3)
    print(sys)
    X = [0.7, 0.3, 0.01]
    Ts = 600:100:1300
    res = solidify(sys, X, Ts)
    print(res)
end


module TestShow
    verbose = false

    import IntertemporalChoiceHeuristics: models, initial_parameters

    io = IOBuffer()
    for model in models
        println(io, model)
        println(io, model(initial_parameters(model)))
        println(io)
    end

    if verbose
        print(takebuf_string(io))
    end
end


_convert_dims(N::Integer, d::Integer) = N-d
_convert_dims(N::Integer, d::Dims)    = N.-d #map(i->_invert_dim(N, i), d)
_convert_dims(N::Integer, d::Integer...) = _convert_dims(N, Dims(d))
_convert_dims(N::Integer, d::Colon) = nothing

_convert_dims(::Dims{N}, d) where N = _convert_dims(N, d)
_convert_dims(::AbstractArray{T,N}, d) where {T,N} = _convert_dims(N, d)

_autokeep(dims::Colon) = false
_autokeep(dims) = true


using Compose

function plot(layout::RQCLayout{VT, PT, SCGate}, vs = collect(vertices(layout))) where {VT, PT}
    vs = sort!(intersect(vs, collect(vertices(layout))))
    qubit_map = gen_qubit_map(layout, vs)
    gate_locs = gen_gate_locs(layout, vs)
    ht = length(vs)
    wd = maximum([maximum(gate_locs[v]) for v in vs]) + 1
    set_default_graphic_size((wd+1)*cm, (ht+1)*cm)
    ctx_gates = Compose.compose(Compose.context())
    ctx_lines = Compose.compose(Compose.context())
    for v in vs
        ctx_lines = Compose.compose(ctx_lines,
            (Compose.context(), line([(0, qubit_map[v]), (wd, qubit_map[v])]), stroke("black"), linewidth(2))
        )
        for d in 1:depth(layout)
            x1 = gate_locs[v][d]
            y1 = qubit_map[v]
            if gates(layout, v)[d] === Noise
                ctx_gates = Compose.compose(ctx_gates, plot_noise(x1, y1))
            elseif gates(layout, v)[d] === Single
                ctx_gates = Compose.compose(ctx_gates, plot_single(x1, y1))
            elseif gates(layout, v)[d] === FSim
                u = partner(layout, v, depth_to_cycle(d))
                if u in vs
                    x2 = gate_locs[u][d]
                    y2 = qubit_map[u]
                    y1 < y2 || continue
                    ctx_gates = Compose.compose(ctx_gates, plot_fsim(x1, y1, x2, y2))
                else
                    ctx_gates = Compose.compose(ctx_gates, plot_fsim_one_side(x1, y1))
                end
            end
        end
    end
    ctx = Compose.compose(
        Compose.context(units = UnitBox(-0.5, -0.5, wd+1, ht+1)),
        ctx_gates,
        ctx_lines)
    return ctx
end

function gen_qubit_map(layout::RQCLayout{VT, PT, SCGate}, vs = collect(vertices(layout))) where {VT, PT}
    vs = sort!(intersect(vs, collect(vertices(layout))))
    qubit_map = Dict(vs[i] => i for i = 1:length(vs))
    return qubit_map
end

function gen_gate_locs(layout::RQCLayout{VT, PT, SCGate}, vs = collect(vertices(layout))) where {VT, PT}
    qubit_map = gen_qubit_map(layout, vs)
    vs = sort!(intersect(vs, collect(vertices(layout))), by = v -> qubit_map[v])
    gate_locs = Dict(v => Float64[] for v in vs)
    frontier = Dict(v => 1 for v in vs)
    for d = 1:depth(layout)
        for v in vs
            if gates(layout, v)[d] in (Single, Id, Noise)
                push!(gate_locs[v], frontier[v])
                frontier[v] += 1
            else
                u = partner(layout, v, depth_to_cycle(d))
                if u in vs
                    # @show u, depth_to_cycle(d)
                    qubit_map[u] > qubit_map[v] || continue
                    loc = max(frontier[u], frontier[v])
                    push!(gate_locs[u], loc)
                    push!(gate_locs[v], loc)
                    for w in vs
                        if qubit_map[v] <= qubit_map[w] <= qubit_map[u]
                            frontier[w] = loc + 1
                        end
                    end
                else
                    push!(gate_locs[v], frontier[v])
                    frontier[v] += 1
                end
            end
        end

    end
    return gate_locs
end

plot_noise(x, y) = (Compose.context(), xgon(x, y, 0.25, 4),
    fill("red"), stroke("red"))
plot_single(x, y) = (Compose.context(), rectangle(x-0.2, y-0.2, 0.4, 0.4),
    fill("black"), stroke("black"))
plot_fsim_one_side(x, y) = (Compose.context(), circle(x, y, 0.2),
    fill("green"), stroke("black"))
plot_fsim(x1, y1, x2, y2) = (Compose.context(),
    (Compose.context(), circle(x1, y1, 0.2), fill("white"), stroke("black")),
    (Compose.context(), circle(x2, y2, 0.2), fill("white"), stroke("black")),
    (Compose.context(), line([(x1, y1), (x2, y2)]), stroke("black"), linewidth(2))
)


function train!(;
    model, ds_train, ds_test, ds_validation,
    loss, optimizer, measures, recorded_measures,
    max_epochs, min_epochs, early_stopping_n, early_stopping_percentage,
)
    for i in 1:max_epochs
        @info "Epoch $i"
        train, test, validation = step!(
            model, ds_train, ds_test, ds_validation;
            loss_function=loss, optimizer, measures,
        )
        @show train
        @show test
        @show validation
        push!(
            recorded_measures,
            merge(prefix_labels.((train, test, validation), (:train_, :test_, :validation_))...),
        )

        let
            global fig, ax = subplots()
            losses = ["train_loss", "test_loss", "validation_loss"]
            plts = foreach(1:3, losses) do i, loss
                ax.plot(
                    getproperty(recorded_measures, loss),
                    label=replace(loss, '_' => ' '),
                )
            end
            ax.legend()
            display(fig)
        end

        if i >= min_epochs
            if argmin(recorded_measures[!, :test_loss]) <= i - early_stopping_n
                @info "Loss has not decreased in the last 10 epochs, stopping training"
                break
            elseif test.loss / train.loss > 1 + early_stopping_percentage / 100
                @info "Test loss more than $early_stopping_percentage% greater than training loss, stopping training"
                break
            end
        end
    end
end


"""Projection of Ω to subspace

Conceptually if ω::Ω represents the unit hypercube id -> [0, 1], an ωπ::ΩProj
represets the element `ω(id)`.  However:
- The application is lazy to actually get element in `[0, 1]`, we use `memrand`
- The application is lazy and can be 'undone', ω can be recovered with parenωπ(ωπ)
- ΩProj supports rand which (i) resolves ωπ at its index, (ii) increments the id
"""
mutable struct ΩProj{O, I} <: Ω{I}
  ω::O
  id::I
end

increment!(ωπ::ΩProj) = ωπ.id = increment(ωπ.id)
parentω(ωπ::ΩProj) = ωπ.ω

# "memrand ωπ::ΩProj to a concrete value"
# function memrand end

@spec rand(ωπ::ΩProj, args...) _res == memrand(_pre(ωπ), args...) "result is resolution of ωπ"
@spec rand(ωπ::ΩProj, args...) _res == ωπ.id == increment(_pre(ωπ.id)) "ωπ id is incremented"

@inline randinc!(ωπ, res) = (increment!(ωπ); res)

# Try Again #
@inline Base.rand(ωπ::ΩProj; rng = Random.GLOBAL_RNG) =
  randinc!(ωπ, memrand(ωπ.ω, ωπ.id, Float64; rng = rng))
@inline Base.rand(ωπ::ΩProj, dims::Dims; rng = Random.GLOBAL_RNG) =
  randinc!(ωπ, memrand(ωπ.ω, ωπ.id, Float64, dims; rng = rng))
@inline Base.rand(ωπ::ΩProj, dim::Integer, dims::Integer...; rng = Random.GLOBAL_RNG) =
  randinc!(ωπ, memrand(ωπ.ω, ωπ.id, Float64, Dims((dim, dims...)); rng = rng))

@inline Base.rand(ωπ::ΩProj, ::Type{T}; rng = Random.GLOBAL_RNG) where T =
  randinc!(ωπ, memrand(ωπ.ω, ωπ.id, T; rng = rng))
@inline Base.rand(ωπ::ΩProj, ::Type{T}, dims::Dims; rng = Random.GLOBAL_RNG) where T =
  randinc!(ωπ, memrand(ωπ.ω, ωπ.id, T, dims; rng = rng))
@inline Base.rand(ωπ::ΩProj, ::Type{T}, dim::Integer, dims::Integer...; rng = Random.GLOBAL_RNG) where T=
  randinc!(ωπ, memrand(ωπ.ω, ωπ.id, T, Dims((dim, dims...)); rng = rng))

@inline Base.rand(ωπ::ΩProj, arr::Array; rng = Random.GLOBAL_RNG) =
  randinc!(ωπ, memrand(ωπ.ω, ωπ.id, arr; rng = rng))
@inline Base.rand(ωπ::ΩProj, arr::Array, dims::Dims; rng = Random.GLOBAL_RNG) =
  randinc!(ωπ, memrand(ωπ.ω, ωπ.id, arr, dims; rng = rng))
@inline Base.rand(ωπ::ΩProj, arr::Array, dim::Integer, dims::Integer...; rng = Random.GLOBAL_RNG) =
  randinc!(ωπ, memrand(ωπ.ω, ωπ.id, arr, Dims((dim, dims...)); rng = rng))

@inline Base.rand(ωπ::ΩProj, ur::UnitRange; rng = Random.GLOBAL_RNG) =
  randinc!(ωπ, memrand(ωπ.ω, ωπ.id, ur; rng = rng))
@inline Base.rand(ωπ::ΩProj, ur::UnitRange, dims::Dims; rng = Random.GLOBAL_RNG) =
  randinc!(ωπ, memrand(ωπ.ω, ωπ.id, ur, dims; rng = rng))
@inline Base.rand(ωπ::ΩProj, ur::UnitRange, dim::Integer, dims::Integer...; rng = Random.GLOBAL_RNG) =
  randinc!(ωπ, memrand(ωπ.ω, ωπ.id, ur, Dims((dim, dims...)); rng = rng))

# Fail Again

# memrand(ωπ::ΩProj, args...) = memrand(ωπ.ω, ωπ.id, args...)

# Ideally
# Base.rand(ωπ::ΩProj, args...; rng = Random.GLOBAL_RNG) =
#   (res = memrand(ωπ.ω, ωπ.id, args...; rng = rng); increment!(ωπ); res)

# e.g. rand((1,2,3))
# Base.rand(ωπ::ΩProj, dims::Dims; rng = Random.GLOBAL_RNG) =
#   (res = memrand(ωπ.ω, ωπ.id, dims; rng = rng); increment!(ωπ); res)

# # e.g. rand(1,2,3)
# Base.rand(ωπ::ΩProj, dims::Integer...; rng = Random.GLOBAL_RNG) =
#   (res = memrand(ωπ.ω, ωπ.id, dims...; rng = rng); increment!(ωπ); res)

# # Base.rand(ωπ::ΩProj; rng = rng) =
# #   (res = memrand(ωπ.ω, dims...); increment!(ωπ); res)

# # e.g: rand(Float64)
# Base.rand(ωπ::ΩProj, ::Type{X} = Float64; rng = Random.GLOBAL_RNG) where {X} =
#   (res = memrand(ωπ.ω, ωπ.id, X; rng = rng); increment!(ωπ); res)

# Projection #

Base.getindex(ωπ::ΩProj{O, I}, i::I) where {O, I} =
  ΩProj{O, I}(ωπ.ω, combine(ωπ.id, i))

Base.getindex(ωπ::ΩProj{O, I}, i::SI) where {O, I, SI} =
  ΩProj{O, I}(ωπ.ω, append(ωπ.id, i))

Base.getindex(ωπ::ΩProj{O, Paired}, i::Int) where O = ΩProj{O, Paired}(ωπ.ω, pair(ωπ.id, i))

function Base.getindex(sω::O, i::Int) where {I, O <: ΩBase{I}}
  ΩProj{O, I}(sω, base(I, i))
end


@reexport module Synapse

using DataStructures: Queue, enqueue!, dequeue!, empty!
using DataStructures: CircularBuffer, fill!, push!, empty!
using Adapt
using CUDA
using LoopVectorization

import ..SpikingNN: excite!, spike!, evaluate!, reset!, isactive

export AbstractSynapse, QueuedSynapse, DelayedSynapse,
       excite!, spike!, reset!, isactive

"""
    AbstractSynapse

Inherit from this type to create a concrete synapse.
"""
abstract type AbstractSynapse end

"""
    excite!(synapse::AbstractSynapse, spikes::Vector{<:Integer})
    excite!(synapses::AbstractArray{<:AbstractSynapse}, spikes::Vector{<:Integer})

Excite a `synapse` with a vector of spikes by calling `excite!(synapse, spike) for spike in spikes`.
"""
excite!(synapse::AbstractSynapse, spikes::Vector{<:Integer}) = map(x -> excite!(synapse, x), spikes)
excite!(synapses::AbstractArray{<:AbstractSynapse}, spikes::Vector{<:Integer}) = map(x -> excite!(synapses, x), spikes)

"""
    spike!(synapse::AbstractSynapse, spike::Integer; dt::Real = 1.0)
    spike!(synapse::AbstractArray{<:AbstractSynapse}, spikes::AbstractArray{<:Integer}; dt::Real = 1.0)

Notify a synapse that the post-synaptic neuron has released a spike.
The default implmentation is to do nothing. Override this behavior by dispatching on your synapse type.
"""
spike!(synapse::AbstractSynapse, spike::Integer; dt::Real = 1.0) = nothing
spike!(synapses::AbstractArray{<:AbstractSynapse}, spikes; dt::Real = 1.0) = nothing

"""
    QueuedSynapse{ST<:AbstractSynapse, IT<:Integer}

A `QueuedSynapse` excites its internal synapse when the timestep matches the head of the queue.
Wrapping a synapse in this type allows you to pre-load several spike excitation times, and the
  internal synapse will be excited as those time stamps are evaluated.
This can be useful for cases where it is more efficient to load all the input spikes before simulation.

*Note: currently only supported on CPU.*
"""
struct QueuedSynapse{ST<:AbstractSynapse, IT<:Integer} <: AbstractSynapse
    core::ST
    queue::Queue{IT}
end
QueuedSynapse{IT}(synapse) where {IT<:Integer} = QueuedSynapse{typeof(synapse), IT}(synapse, Queue{IT}())
QueuedSynapse(synapse) = QueuedSynapse{typeof(synapse), Int}(synapse, Queue{Int}())

_ispending(queue, t) = !isempty(queue) && first(queue) <= t
function _shiftspike!(queue, lastspike, t)
    while _ispending(queue, t)
        lastspike = dequeue!(queue)
    end

    return lastspike
end
function _shiftspike!(queues::AbstractArray, lastspikes, t)
    pending = map(x -> _ispending(x, t), queues)
    while any(pending)
        @. lastspikes[pending] = dequeue!(queues[pending])
        pending = map(x -> _ispending(x, t), queues)
    end

    return lastspikes
end

"""
    excite!(synapse::QueuedSynapse, spike::Integer)
    excite!(synapses::AbstractArray{<:QueuedSynapse}, spike::Integer)

Excite `synapse` with a `spike` (`spike` == time step of spike) by pushing
  `spike` onto `synapse.queue`.
"""
excite!(synapse::QueuedSynapse, spike::Integer) = enqueue!(synapse.queue, spike)
excite!(synapses::T, spike::Integer) where T<:AbstractArray{<:QueuedSynapse} =
    map(x -> enqueue!(x, spike), synapses.queue)

isactive(synapse::QueuedSynapse, t::Integer; dt::Real = 1.0) = _ispending(synapse.queue, t) || isactive(synapse.core, t; dt = dt)
isactive(synapses::T, t::Integer; dt::Real = 1.0) where T<:AbstractArray{<:QueuedSynapse} =
    any(map(x -> _ispending(x, t), synapses.queue)) || isactive(synapses.core, t; dt = dt)

"""
    evaluate!(synapse::QueuedSynapse, t::Integer; dt::Real = 1.0)
    (synapse::QueuedSynapse)(t::Integer; dt::Real = 1.0)
    evaluate!(synapses::AbstractArray{<:QueuedSynapse}, t::Integer; dt::Real = 1.0)

Evaluate `synapse` at time `t` by first exciting `synapse.core` with a spike if
  there is one to process, then evaluating `synapse.core`.
"""
function evaluate!(synapse::QueuedSynapse, t::Integer; dt::Real = 1.0)
    excite!(synapse.core, _shiftspike!(synapse.queue, 0, t))

    return synapse.core(t; dt = dt)
end
(synapse::QueuedSynapse)(t::Integer; dt::Real = 1.0) = evaluate!(synapse, t; dt = dt)
function evaluate!(synapses::T, t::Integer; dt::Real = 1.0) where T<:AbstractArray{<:QueuedSynapse}
    lastspikes = _shiftspike!(synapses.queue, zeros(Int, size(synapses)), t)
    @inbounds for i in eachindex(synapses)
        excite!(view(synapses.core, i), lastspikes[i])
    end

    return evaluate!(synapses.core, t; dt = dt)
end

"""
    reset!(synapse::QueuedSynapse)
    reset!(synapses::AbstractArray{<:QueuedSynapse})

Clear `synapse.queue` and reset `synapse.core`.
"""
function reset!(synapse::QueuedSynapse)
    empty!(synapse.queue)
    reset!(synapse.core)
end
function reset!(synapses::T) where T<:AbstractArray{<:QueuedSynapse}
    empty!.(synapses.queue)
    reset!(synapses.core)
end

"""
    DelayedSynapse

A `DelayedSynapse` adds a fixed delay to spikes when exciting its internal synapse.
"""
struct DelayedSynapse{T<:Real, ST<:AbstractSynapse} <: AbstractSynapse
    core::ST
    delay::T
end

"""
    excite!(synapse::DelayedSynapse, spike::Integer)
    excite!(synapses::AbstractArray{<:DelayedSynapse}, spike::Integer)

Excite `synapse.core` with a `spike` + `synapse.delay` (`spike` == time step of spike).
"""
excite!(synapse::DelayedSynapse, spike::Integer) = excite!(synapse.core, spike + synapse.delay)
function excite!(synapses::T, spike::Integer) where T<:AbstractArray{<:DelayedSynapse}
    delayedspikes = adapt(Array{eltype(synapses.delay), ndims(synapses)}, spike .+ synapses.delay)
    if spike > 0
        @inbounds for i in eachindex(synapses)
            excite!(view(synapses.core, i), delayedspikes[i])
        end
    end
end

isactive(synapse::DelayedSynapse, t::Integer; dt::Real = 1.0) = isactive(synapse.core, t; dt = dt)
isactive(synapses::T, t::Integer; dt::Real = 1.0) where T<:AbstractArray{<:DelayedSynapse} =
    isactive(synapses.core, t; dt = dt)

"""
    evaluate!(synapse::DelayedSynapse, t::Integer; dt::Real = 1.0)
    (synapse::DelayedSynapse)(t::Integer; dt::Real = 1.0)
    evaluate!(synapses::AbstractArray{<:DelayedSynapse}, t::Integer; dt::Real = 1.0)

Evaluate `synapse.core` at time `t`.
"""
evaluate!(synapse::DelayedSynapse, t::Integer; dt::Real = 1.0) = evaluate!(synapse.core, t; dt = dt)
(synapse::DelayedSynapse)(t::Integer; dt::Real = 1.0) = evaluate!(synapse, t; dt = dt)
evaluate!(synapses::T, t::Integer; dt::Real = 1.0) where T<:AbstractArray{<:DelayedSynapse} =
    evaluate!(synapses.core, t; dt = dt)
evaluate!(current, synapses::T, t::Integer; dt::Real = 1.0) where T<:AbstractArray{<:DelayedSynapse} =
    evaluate!(current, synapses.core, t; dt = dt)

"""
    reset!(synapse::DelayedSynapse)
    reset!(synapses::AbstractArray{<:DelayedSynapse})

Reset `synapse.core`.
"""
reset!(synapse::DelayedSynapse) = reset!(synapse.core)
reset!(synapses::T) where T<:AbstractArray{<:DelayedSynapse} = reset!(synapses.core)

"""
    Delta{IT<:Integer, VT<:Real}

A synapse representing a Dirac-delta at `lastspike` with amplitude `q`.
"""
mutable struct Delta{IT<:Integer, VT<:Real} <: AbstractSynapse
    lastspike::VT
    q::VT
end

"""
    Delta{IT, VT}(;q::Real = 1)
    Delta(;q::Real = 1)

Create a Dirac-delta synapse with amplitude `q`.
"""
Delta{IT, VT}(;q::Real = 1) where {IT<:Integer, VT<:Real} = Delta{IT, VT}(-Inf, q)
Delta(;q::Real = 1) = Delta{Int, Float32}(q = q)

"""
    excite!(synapse::Delta, spike::Integer)
    excite!(synapses::AbstractArray{<:Delta}, spike::Integer)

Excite `synapse` with a `spike` (`spike` == time step of spike).
"""
excite!(synapse::Delta, spike::Integer) = (spike > 0) && (synapse.lastspike = spike)
excite!(synapses::T, spike::Integer) where T<:AbstractArray{<:Delta} = (spike > 0) && (synapses.lastspike .= spike)

isactive(synapse::Delta, t::Integer; dt::Real = 1.0) = (t * dt == synapse.lastspike)
isactive(synapses::T, t::Integer; dt::Real = 1.0) where T<:AbstractArray{<:Delta} = any(t * dt .== synapses.lastspike)

"""
    delta(t::Real, lastspike, q)
    delta(t::Real, lastspike::AbstractArray{<:Real}, q::AbstractArray{<:Real})
    delta(t::Real, lastspike::CuVecOrMat{<:Real}, q::CuVecOrMat{<:Real})

Evaluate a Dirac-delta synapse.
Use `CuVector` instead of `Vector` for GPU support.

# Fields
- `t`: current time
- `lastspike`: last pre-synaptic spike time
- `q`: amplitude
"""
delta(t::Real, q) = (t ≈ 0) ? q : zero(q)
delta(t::AbstractArray{<:Real}, q::AbstractArray{<:Real}) = delta.(t, q)

"""
    evaluate!(synapse::Delta, t::Integer; dt::Real = 1.0)
    (synapse::Delta)(t::Integer; dt::Real = 1.0)
    evaluate!(synapses::AbstractArray{<:Delta}, t::Integer; dt::Real = 1.0)

Return `synapse.q` if `t == synapse.lastspike` otherwise return zero.
"""
evaluate!(synapse::Delta, t::Integer; dt::Real = 1.0) = delta((t - synapse.lastspike) * dt, synapse.q)
(synapse::Delta)(t::Integer; dt::Real = 1.0) = evaluate!(synapse, t; dt = dt)
evaluate!(synapses::T, t::Integer; dt::Real = 1.0) where T<:AbstractArray{<:Delta} =
    delta(t * dt, synapses.lastspike * dt, synapses.q)

"""
    reset!(synapse::Delta)
    reset!(synapses::AbstractArray{<:Delta})

Reset `synapse`.
"""
reset!(synapse::Delta) = (synapse.lastspike = -Inf)
reset!(synapses::T) where T<:AbstractArray{<:Delta} = (synapses.lastspike .= -Inf)

"""
    Alpha{IT<:Integer, VT<:Real}

Synapse that returns `(t - lastspike) * (q / τ) * exp(-(t - lastspike - τ) / τ) Θ(t - lastspike)`
(where `Θ` is the Heaviside function).
"""
mutable struct Alpha{IT<:Integer, VT<:Real} <: AbstractSynapse
    lastspike::VT
    q::VT
    τ::VT
end

"""
    Alpha{IT, VT}(;q::Real = 1, τ::Real = 1)
    Alpha(;q::Real = 1, τ::Real = 1)

Create an alpha synapse with amplitude `q` and time constant `τ`.
"""
Alpha{IT, VT}(;q::Real = 1, τ::Real = 1) where {IT<:Integer, VT<:Real} = Alpha{IT, VT}(-Inf, q, τ)
Alpha(;q::Real = 1, τ::Real = 1) = Alpha{Int, Float32}(q = q, τ = τ)

"""
    excite!(synapse::Alpha, spike::Integer)
    excite!(synapses::AbstractArray{<:Alpha}, spike::Integer)

Excite `synapse` with a `spike` (`spike` == time step of spike).
"""
excite!(synapse::Alpha, spike::Integer) = (spike > 0) && (synapse.lastspike = spike)
excite!(synapses::T, spike::Integer) where T<:AbstractArray{<:Alpha} = (spike > 0) && (synapses.lastspike .= spike)

isactive(synapse::Alpha, t::Real; dt::Real = 1.0) = dt * (t - synapse.lastspike) <= 10 * synapse.τ
isactive(synapses::T, t::Integer; dt::Real = 1.0) where T<:AbstractArray{<:Alpha} =
    any(dt .* (t .- synapses.lastspike) .<= 10 .* synapses.τ)

"""
    alpha(t::Real, lastspike, q, tau)
    alpha(t::Real, lastspike::AbstractArray{<:Real}, q::AbstractArray{<:Real}, tau::AbstractArray{<:Real})
    alpha(t::Real, lastspike::CuVecOrMat{<:Real}, q::CuVecOrMat{<:Real}, tau::CuVecOrMat{<:Real})

Evaluate an alpha synapse. Modeled as `(t - lastspike) * (q / τ) * exp(-(t - lastspike - τ) / τ) Θ(t - lastspike)`
  (where `Θ` is the Heaviside function).
Use `CuVector` instead of `Vector` for GPU support.

# Fields
- `t`: current time
- `lastspike`: last pre-synaptic spike time
- `q`: amplitude
- `tau`: time constant
"""
function alpha(t::Real, lastspike, q, tau)
    Δ = t - lastspike

    return (Δ >= 0 && Δ < Inf) * Δ * (q / tau) * exp(-(Δ - tau) / tau)
end
function alpha(t::Real, lastspike::AbstractArray{<:Real}, q::AbstractArray{<:Real}, tau::AbstractArray{<:Real})
    Δ = t .- lastspike
    I = @avx @. Δ * (q / tau) * exp(-(Δ - tau) / tau)

    return map((δ, i) -> (δ >= 0) && (δ < Inf) ? δ * i : zero(i), Δ, I)
end
function alpha(t::Real, lastspike::CuVecOrMat{<:Real}, q::CuVecOrMat{<:Real}, tau::CuVecOrMat{<:Real})
    Δ = t .- lastspike

    return @. (Δ >= 0) * (Δ < Inf) * Δ * (q / tau) * exp(-(Δ - tau) / tau)
end

"""
    evaluate!(synapse::Alpha, t::Integer; dt::Real = 1.0)
    (synapse::Alpha)(t::Integer; dt::Real = 1.0)
    evaluate!(synapses::AbstractArray{<:Alpha}, t::Integer; dt::Real = 1.0)

Evaluate an alpha synapse. See [`Synapse.Alpha`](@ref).
"""
evaluate!(synapse::Alpha, t::Integer; dt::Real = 1.0) = alpha(t * dt, synapse.lastspike * dt, synapse.q, synapse.τ)
(synapse::Alpha)(t::Integer; dt::Real = 1.0) = evaluate!(synapse, t; dt = dt)
evaluate!(synapses::T, t::Integer; dt::Real = 1.0) where T<:AbstractArray{<:Alpha} =
    alpha(t * dt, synapses.lastspike * dt, synapses.q, synapses.τ)

"""
    reset!(synapse::Alpha)
    reset!(synapses::AbstractArray{<:Alpha})

Reset `synapse`.
"""
reset!(synapse::Alpha) = (synapse.lastspike = -Inf)
reset!(synapses::T) where T<:AbstractArray{<:Alpha}= (synapses.lastspike .= -Inf)

"""
    EPSP{T<:Real}

Synapse that returns `(ϵ₀ / τm - τs) * (exp(-Δ / τm) - exp(-Δ / τs)) Θ(Δ)`
(where `Θ` is the Heaviside function and `Δ = t - lastspike`).

Specifically, this is the EPSP time course for the SRM0 model introduced by Gerstner.
Details: [Spiking Neuron Models: Single Neurons, Populations, Plasticity]
         (https://icwww.epfl.ch/~gerstner/SPNM/node27.html#SECTION02323400000000000000)
"""
mutable struct EPSP{IT<:Integer, VT<:Real} <: AbstractSynapse
    spikes::CircularBuffer{VT}
    ϵ₀::VT
    τm::VT
    τs::VT
end

"""
    EPSP{IT, VT}(;ϵ₀::Real = 1, τm::Real = 1, τs::Real = 1, N = 100)
    EPSP(;ϵ₀::Real = 1, τm::Real = 1, τs::Real = 1, N = 100)

Create an EPSP synapse with amplitude `ϵ₀`, rise time `τs`, and fall time `τm`.
Specify `N` to adjust how many pre-synaptic spikes are remembered between post-synaptic spikes.
"""
EPSP{IT, VT}(;ϵ₀::Real = 1, τm::Real = 1, τs::Real = 1, N = 100) where {IT<:Integer, VT<:Real} =
    EPSP{IT, VT}(fill!(CircularBuffer{VT}(N), -Inf), ϵ₀, τm, τs)
EPSP(;ϵ₀::Real = 1, τm::Real = 1, τs::Real = 1, N = 100) = EPSP{Int, Float32}(ϵ₀ = ϵ₀, τm = τm, τs = τs, N = N)

"""
    excite!(synapse::EPSP, spike::Integer)
    excite!(synapses::AbstractArray{<:EPSP}, spike::Integer)

Excite `synapse` with a `spike` (`spike` == time step of spike).
"""
excite!(synapse::EPSP, spike::Integer) = (spike > 0) && push!(synapse.spikes, spike)
excite!(synapses::T, spike::Integer) where T<:AbstractArray{<:EPSP} = (spike > 0) && push!.(synapses.spikes, spike)

"""
    spike!(synapse::EPSP, spike::Integer; dt::Real = 1.0)
    spike!(synapses::AbstractArray{<:EPSP}, spikes; dt::Real = 1.0)

Reset `synapse` when the post-synaptic neuron spikes.
"""
spike!(synapse::EPSP, spike::Integer; dt::Real = 1.0) = reset!(synapse)
spike!(synapses::T, spikes; dt::Real = 1.0) where T<:AbstractArray{<:EPSP} = reset!(synapses)

isactive(synapse::EPSP, t::Integer; dt::Real) = dt * (t - first(synapse.spikes)) <= synapse.τs + 8 * synapse.τm
isactive(synapses::T, t::Integer; dt::Real = 1.0) where T<:AbstractArray{<:EPSP} =
    any(dt .* (t .- first.(synapses.spikes)) .<= synapses.τs .+ 8 .* synapses.τm)

"""
    epsp(t::Real, lastspike, q, taum, taus)
    epsp(t::Real, lastspike::AbstractArray{<:Real}, q::AbstractArray{<:Real}, taum::AbstractArray{<:Real}, taus::AbstractArray{<:Real})
    epsp(t::Real, lastspike::CuVecOrMat{<:Real}, q::CuVecOrMat{<:Real}, taum::CuVecOrMat{<:Real}, taus::CuVecOrMat{<:Real})

Evaluate an EPSP synapse. Modeled as `(ϵ₀ / τm - τs) * (exp(-Δ / τm) - exp(-Δ / τs)) Θ(Δ)`
  (where `Θ` is the Heaviside function and `Δ = t - lastspike`).
Specifically, this is the EPSP time course for the SRM0 model introduced by Gerstner.
Details: [Spiking Neuron Models: Single Neurons, Populations, Plasticity]
         (https://icwww.epfl.ch/~gerstner/SPNM/node27.html#SECTION02323400000000000000)

Use `CuVector` instead of `Vector` for GPU support.

# Fields
- `t`: current time
- `lastspike`: last pre-synaptic spike time
- `q`: amplitude
- `taum`: rise time constant
- `taus`: fall time constant
"""
function epsp(t::Real, lastspike, q, taum, taus)
    Δ = t - lastspike

    return (Δ >= 0 && Δ < Inf && taus != taum) * q / (1 - taus / taum) * (exp(-Δ / taum) - exp(-Δ / taus))
end
function epsp(t::Real, lastspike::AbstractArray{<:Real}, q::AbstractArray{<:Real}, taum::AbstractArray{<:Real}, taus::AbstractArray{<:Real})
    Δ = t .- lastspike
    I = @. q / (1 - taus / taum) * (exp(-Δ / taum) - exp(-Δ / taus))

    return map((ts, tm, δ, i) -> (δ >= 0) && (δ < Inf) && (ts != tm) ? i : zero(i), taus, taum, Δ, I)
end
function epsp(t::Real, lastspike::CuVecOrMat{<:Real}, q::CuVecOrMat{<:Real}, taum::CuVecOrMat{<:Real}, taus::CuVecOrMat{<:Real})
    Δ = t .- lastspike

    return @. (Δ >= 0) * (Δ < Inf) * (taus != taum) * q / (1 - taus / taum) * (exp(-Δ / taum) - exp(-Δ / taus))
end

"""
    evaluate!(synapse::EPSP, t::Integer; dt::Real = 1.0)
    (synapse::EPSP)(t::Integer; dt::Real = 1.0)
    evaluate!(synapses::AbstractArray{<:EPSP}, t::Integer; dt::Real = 1.0)

Evaluate an EPSP synapse. See [`Synapse.EPSP`](@ref).
"""
evaluate!(synapse::EPSP, t::Integer; dt::Real = 1.0) =
    mapreduce(tf -> epsp(t * dt, tf * dt, synapse.ϵ₀, synapse.τm, synapse.τs), +, synapse.spikes)
(synapse::EPSP)(t::Integer; dt::Real = 1.0) = evaluate!(synapse, t; dt = dt)
function evaluate!(synapses::T, t::Integer; dt::Real = 1.0) where T<:AbstractArray{<:EPSP}
    N = length(synapses.spikes[1])
    return mapreduce(i -> epsp(t * dt, adapt(typeof(synapses.ϵ₀), getindex.(synapses.spikes, i) * dt), synapses.ϵ₀, synapses.τm, synapses.τs), +, 1:N)
end

"""
    reset!(synapse::EPSP)
    reset!(synapses::AbstractArray{<:EPSP})

Reset `synapse`.
"""
reset!(synapse::EPSP) = fill!(empty!(synapse.spikes), -Inf)
reset!(synapses::T) where T<:AbstractArray{<:EPSP}= fill!.(empty!.(synapses.spikes), -Inf)

end


@blasfun clblasSaxpy(n::Csize_t, alpha::cl.CL_float,
                     X::cl.CL_mem, offx::Csize_t, incx::Cint,
                     Y::cl.CL_mem, offy::Csize_t, incy::Cint,
                     n_queues::cl.CL_uint,
                     queues::Ptr{cl.CL_command_queue},
                     n_events_in_wait_list::cl.CL_uint,
                     event_wait_list::Ptr{cl.CL_event},
                     events::Ptr{cl.CL_event})

@blasfun2 clblasSaxpy(n::Csize_t, alpha::cl.CL_float,
                      X::cl.CL_mem, offx::Csize_t, incx::Cint,
                      Y::cl.CL_mem, offy::Csize_t, incy::Cint)

@blasfun clblasDaxpy(n::Csize_t, alpha::cl.CL_double,
                     X::cl.CL_mem, offx::Csize_t, incx::Cint,
                     Y::cl.CL_mem, offy::Csize_t, incy::Cint,
                     n_queues::cl.CL_uint,
                     queues::Ptr{cl.CL_command_queue},
                     n_events_in_wait_list::cl.CL_uint,
                     event_wait_list::Ptr{cl.CL_event},
                     events::Ptr{cl.CL_event})

@blasfun2 clblasDaxpy(n::Csize_t, alpha::cl.CL_double,
                      X::cl.CL_mem, offx::Csize_t, incx::Cint,
                      Y::cl.CL_mem, offy::Csize_t, incy::Cint)

@blasfun clblasCaxpy(n::Csize_t, alpha::CL_float2,
                     X::cl.CL_mem, offx::Csize_t, incx::Cint,
                     Y::cl.CL_mem, offy::Csize_t, incy::Cint,
                     n_queues::cl.CL_uint,
                     queues::Ptr{cl.CL_command_queue},
                     n_events_in_wait_list::cl.CL_uint,
                     event_wait_list::Ptr{cl.CL_event},
                     events::Ptr{cl.CL_event})

@blasfun2 clblasCaxpy(n::Csize_t, alpha::CL_float2,
                      X::cl.CL_mem, offx::Csize_t, incx::Cint,
                      Y::cl.CL_mem, offy::Csize_t, incy::Cint)

@blasfun clblasZaxpy(n::Csize_t, alpha::CL_double2,
                     X::cl.CL_mem, offx::Csize_t, incx::Cint,
                     Y::cl.CL_mem, offy::Csize_t, incy::Cint,
                     n_queues::cl.CL_uint,
                     queues::Ptr{cl.CL_command_queue},
                     n_events_in_wait_list::cl.CL_uint,
                     event_wait_list::Ptr{cl.CL_event},
                     events::Ptr{cl.CL_event})

@blasfun2 clblasZaxpy(n::Csize_t, alpha::CL_double2,
                      X::cl.CL_mem, offx::Csize_t, incx::Cint,
                      Y::cl.CL_mem, offy::Csize_t, incy::Cint)


# The GeoJSON Format Specification (Revision 1.0, 16 June 2008)
# url: http://geojson.org/geojson-spec.html

# Coordinate Reference System Objects
# (has keys "type" and "properties")
typealias CRS Union{Void, Dict}
# TODO: Handle full CRS spec

# Position
# (x, y, [z, ...]) - meaning of additional elements undefined.
# In an object's contained geometries, Positions must have uniform dimensions.
# A bbox must match that dimensionality.
# Both requirements are currently unchecked.
typealias Position Vector{Float64}

# Abstract Interfaces

abstract AbstractGeoJSON

hasbbox(obj::AbstractGeoJSON) = isdefined(obj, :bbox)
hascrs(obj::AbstractGeoJSON) = isdefined(obj, :crs)
bbox(obj::AbstractGeoJSON) = obj.bbox
crs(obj::AbstractGeoJSON) = obj.crs

abstract Geometry <: AbstractGeoJSON

# Geometry Objects

type Point <: Geometry
    coordinates::Position
    # optional
    bbox::Vector{Float64}
    crs::CRS

    Point(coordinates = Float64[]; kwargs...) = fill_options!(new(coordinates); kwargs...)
end

type MultiPoint <: Geometry
    coordinates::Vector{Position}
    # optional
    bbox::Vector{Float64}
    crs::CRS

    MultiPoint(coordinates = Position[]; kwargs...) = fill_options!(new(coordinates); kwargs...)
end

type LineString <: Geometry
    coordinates::Vector{Position} # two or more positions
    # optional
    bbox::Vector{Float64}
    crs::CRS

    LineString(coordinates = Position[]; kwargs...) = fill_options!(new(coordinates); kwargs...)
end

type MultiLineString <: Geometry
    coordinates::Vector{Vector{Position}} # vector{vector of two or more positions}
    # optional
    bbox::Vector{Float64}
    crs::CRS

    MultiLineString(coordinates = Vector{Position}[]; kwargs...) = fill_options!(new(coordinates); kwargs...)
end

type Polygon <: Geometry
    coordinates::Vector{Vector{Position}} # vector{'LinearRing' vectors, exterior ring first}
    # optional
    bbox::Vector{Float64}
    crs::CRS

    Polygon(coordinates = Vector{Position}[]; kwargs...) = fill_options!(new(coordinates); kwargs...)
end

type MultiPolygon <: Geometry
    coordinates::Vector{Vector{Vector{Position}}} # vector{vector{'LinearRing' vectors, exterior ring first}}
    # optional
    bbox::Vector{Float64}
    crs::CRS

    MultiPolygon(coordinates = Vector{Vector{Position}}[]; kwargs...) = fill_options!(new(coordinates); kwargs...)
end

type GeometryCollection <: Geometry
    geometries::Vector{Geometry}
    # optional
    bbox::Vector{Float64}
    crs::CRS

    GeometryCollection(geometries = Geometry[]; kwargs...) =
        fill_options!(new(geometries); kwargs...)
end

# Feature Objects

type Feature <: AbstractGeoJSON
    geometry::Union{Void, Geometry}
    properties::Union{Void, Dict}
    # optional
    bbox::Vector{Float64}
    crs::CRS
    id

    Feature(
            geometry::Union{Void, Geometry}=nothing,
            properties::Union{Void, Dict}=nothing;
            kwargs...
        ) =
        fill_options!(new(geometry, properties); kwargs...)
end
hasid(obj::Feature) = isdefined(obj, :id)

type FeatureCollection <: AbstractGeoJSON
    features::Vector{Feature}
    # optional
    bbox::Vector{Float64}
    crs::CRS

    FeatureCollection(features::Vector{Feature} = Feature[]; kwargs...) =
        fill_options!(new(features); kwargs...)
end

# Helper Functions

function fill_options!(obj::AbstractGeoJSON, param::AbstractString, value)
    if param == "id"
        obj.id = value
    elseif param == "bbox"
        obj.bbox = value
    elseif param == "crs"
        obj.crs = value
    end
    obj
end

function fill_options!(obj::AbstractGeoJSON, param::Symbol, value)
    if param == :id
        obj.id = value
    elseif param == :bbox
        obj.bbox = value
    elseif param == :crs
        obj.crs = value
    end
    obj
end

function fill_options!(obj::AbstractGeoJSON; kwargs...)
    for (param, value) in kwargs
        fill_options!(obj, param, value)
    end
    obj
end

function fill_options!(obj::AbstractGeoJSON, kwargs::Dict)
    for (param, value) in kwargs
        fill_options!(obj, param, value)
    end
    obj
end

# Additional Constructors (Dict -> GeoJSON)

function GeometryCollection(obj::Dict)
    collection = GeometryCollection()
    geometries = obj["geometries"]
    sizehint!(collection.geometries, length(geometries))
    for geometry in geometries
        push!(collection.geometries, dict2geojson(geometry))
    end
    fill_options!(collection, obj)
end

for geom in (:MultiPolygon, :Polygon, :MultiLineString, :LineString, :MultiPoint, :Point)
    @eval $(geom)(obj::Dict) = fill_options!($(geom)(obj["coordinates"]), obj)
end

function Feature(obj::Dict)
    feature = Feature(dict2geojson(obj["geometry"]), obj["properties"])
    if haskey(obj, "id")
        feature.id = obj["id"]
    end
    fill_options!(feature, obj)
end

function FeatureCollection(obj::Dict)
    features = obj["features"]
    collection = FeatureCollection(Feature[])
    sizehint!(collection.features, length(features))
    for feature in features
        push!(collection.features, Feature(feature))
    end
    fill_options!(collection, obj)
end

# Implement GEO interface

for (geom,attributes) in ((MultiPolygon, (:coordinates,)),
                          (Polygon, (:coordinates,)),
                          (MultiLineString, (:coordinates,)),
                          (LineString, (:coordinates,)),
                          (MultiPoint, (:coordinates,)),
                          (Point, (:coordinates,)),
                          (GeometryCollection, (:geometries,)),
                          (Feature, (:geometry, :properties, :id)),
                          (FeatureCollection, (:features,)))
    for attr in attributes
        @eval $(attr)(obj::$geom) = obj.$(attr)
    end
end


using DifferentiableStateSpaceModels, BenchmarkTools
using Test, Parameters, LinearAlgebra, Random

using DifferentiableStateSpaceModels
using Test, LinearAlgebra

# The BLAS threads is still an issue in Julia 1.7
# This has no effect with MKL
DifferentiableStateSpaceModels.set_blas_threads()

println("Running Testsuite with Threads.nthreads() = $(Threads.nthreads()) BLAS.vendor = $(BLAS.vendor()), and BLAS.num_threads = $(BLAS.get_num_threads()) \n")

# Setting miniumum number of evalations to avoid compilation
BenchmarkTools.DEFAULT_PARAMETERS.evals = 3  # at least 3

# Benchmark groups
const SUITE = BenchmarkGroup()
SUITE["utilities"] = include(pkgdir(DifferentiableStateSpaceModels) *
                             "/benchmark/utilities.jl")
SUITE["rbc"] = include(pkgdir(DifferentiableStateSpaceModels) * "/benchmark/rbc.jl")
SUITE["sgu"] = include(pkgdir(DifferentiableStateSpaceModels) * "/benchmark/sgu.jl")
SUITE["fvgq"] = include(pkgdir(DifferentiableStateSpaceModels) * "/benchmark/fvgq.jl")
#SUITE["rbc_likelihoods"] =
# include(pkgdir(DifferentiableStateSpaceModels) * "/benchmark/rbc_likelihoods.jl")

# See README.md
# Can execute with:
# results = run(SUITE; verbose = true)

#Then extract with things like:
# median(results["rbc_likelihoods"])
# results["rbc_likelihoods"]["joint"]["likelihood"]
# Or save stuff, make a change, and then use `judge(m1, m2)`


VERSION < v"0.7.0-rc1" && __precompile__()
module RangeArrays

using Compat

include("matrix.jl")
include("repeatedrange.jl")

export RangeMatrix, RepeatedRangeMatrix

end # module


module PrettyEinsum
using TensorOperations

export @Σein!_str, @Σein_str, @Σein!, @Σein

include("parse.jl")
include("macrostr.jl")
include("macro.jl")

end


#module ListItem

#export ListIt, Node, EmptyNode

abstract type Node end

mutable struct EmptyNode <: Node
  next::Node
  previous::Node
  EmptyNode() = (x=new(); x.next=x; x.previous=x; x)
end
# aa=EmptyNode()

mutable struct ListIt <: Node
    next           :: Node
    previous       :: Node
    bucketSizeRow  :: Int
    MAXBUCKETS     :: Int
    bucketTotal    :: Array{Float64,1}
    bucketVariance :: Array{Float64,1}

    function ListIt()
        x = new()
        x.next = x
        x.previous = x
        x.bucketSizeRow = 1
        x.MAXBUCKETS = 1000
        x.bucketTotal = zeros(x.MAXBUCKETS+1)
        x.bucketVariance = zeros(x.MAXBUCKETS+1)
        x
    end
end

function insertBucket!(lst_it::Node, value::Float64, variance::Float64)
    k = lst_it.bucketSizeRow
    lst_it.bucketSizeRow += 1
    setTotal!(lst_it, value, k)
    setVariance!(lst_it, variance, k)
end

function removeBucket(lst_it::ListIt)
    # Removes the first Bucket
    compressBucketsRow(lst_it, 1);
end

function compressBucketsRow!(lst_it::ListIt, numItemsDeleted::Int)
    for k = numItemsDeleted : lst_it.MAXBUCKETS
        lst_it.bucketTotal[k-numItemsDeleted] = lst_it.bucketTotal[k]
        lst_it.bucketVariance[k-numItemsDeleted]=lst_it.bucketVariance[k]
    end
    for k = 1 : numItemsDeleted
        clearBucket!(lst_it, lst_it.MAXBUCKETS - k + 1)
    end
    lst_it.bucketSizeRow -= numItemsDeleted
end

function measureByteSize(lst_it::ListIt)
    # int size = (int) SizeOf.sizeOf(this); return size;
end

function total(lst_it::ListIt, k::Int)
    lst_it.bucketTotal[k]
end

function variance(lst_it::ListIt, k::Int)
    lst_it.bucketVariance[k]
end

function setTotal!(lst_it::ListIt, val::Float64, k::Int)
    lst_it.bucketTotal[k] = val
end

function setVariance!(lst_it::ListIt, val::Float64, k::Int)
    lst_it.bucketVariance[k] = val
end

function clearBucket!(lst_it::ListIt, k::Int)
    setTotal!(lst_it, 0, k)
    setVariance!(lst_it, 0, k)
end

function clear!(lst_it::ListIt)
    lst_it.bucketSizeRow = 0
    for k = 1 : lst_it.MAXBUCKETS
        clearBucket!(lst_it, k)
    end
end
#end # module
###############################################################################

#using ListItem

""" Pointers to the head and tail of the linked list  """
mutable struct List
    count :: Int
    head  :: Node
    tail  :: Node
    function List()
        x = new()
        x.count = 0
        x.head = ListIt() # EmptyNode()
        x.tail = ListIt() # EmptyNode()
        x
    end
end

function clearLst!(lst::List)
  # public void clear()
  # post: clears the list so that it contains no elements.
  lst.head = EmptyNode()
  lst.tail = EmptyNode()
  lst.count = 0
end

function addToHead!(lst::List)
  a = ListIt()
  lst.head.previous = a
  a.next = lst.head
  lst.head = a
  if typeof(lst.tail) <: EmptyNode
    println("Yep: typeof(lst.tail) <: EmptyNode")
    lst.tail = lst.head
    println("Nope: typeof(lst.tail) <: EmptyNode")
  end
  lst.count += 1
end

function removeFromHead!(lst::List)
  # pre: list is not empty
  # post: removes and returns first object from the list
  lst.head = lst.head.next
  if !(typeof(lst.head) <: EmptyNode)
    lst.head.previous = lst.head.previous #EmptyNode()
  else
    lst.tail = EmptyNode()
  end
  lst.count -= 1
  lst.head
end

function addToTail!(lst::List)
  # line 94:
  # public void addToTail() {
  #  pre: anObject is non-null
  # post: the object is added at the end of the list
  b = ListIt()
  lst.tail.next = b
  b.previous = lst.tail
  lst.tail = b
  if typeof(lst.head) <: EmptyNode
    lst.head = lst.tail
  end
  lst.count += 1
end

function removeFromTail!(lst::List)
  #   public void removeFromTail() {
  # //			pre: list is not empty
  # //			post: the last object in the list is removed and returned
  # //			ListItem temp;
  # //			temp = this.tail;
  lst.tail = lst.tail.previous
  #if typeof(lst.tail) <: EmptyNode
  #  lst.head = EmptyNode()
  #else
  lst.tail.next = lst.tail.next
  #end
  lst.count -= 1
end
function myCounter(lst_it::Node, counter=1)

  # println("... myCounter: counter = $counter")
  if typeof(lst_it) <: EmptyNode || lst_it.next == lst_it
    return counter
  else
    if lst_it == lst_it.next.previous
      myCounter(lst_it.next, counter+1)
    else
      return counter
    end
  end
end


using CoordinateTransformations, Rotations, TestImages, ImageCore, StaticArrays, OffsetArrays, Interpolations, LinearAlgebra
using Test, ReferenceTests

refambs = detect_ambiguities(CoordinateTransformations, Base, Core)
using ImageTransformations
ambs = detect_ambiguities(ImageTransformations, CoordinateTransformations, Base, Core)
@test isempty(setdiff(ambs, refambs))

function typestring(::Type{T}) where T   # from https://github.com/JuliaImages/ImageCore.jl/pull/133
    buf = IOBuffer()
    show(buf, T)
    String(take!(buf))
end

# helper function to compare NaN
nearlysame(x, y) = x ≈ y || (isnan(x) & isnan(y))
nearlysame(A::AbstractArray, B::AbstractArray) = all(map(nearlysame, A, B))

tests = [
    "autorange.jl",
    "resizing.jl",
    "interpolations.jl",
    "warp.jl",
    "deprecated.jl" # test deprecations in the last
]

@testset "ImageTransformations" begin
for t in tests
    @testset "$t" begin
        include(t)
    end
end
end

nothing


export HardScore

"""
    HardScore{I} <: Score{I}

A fixed score.
"""
struct HardScore{I} <: Score{I}
    value :: I
end

@impl begin
    struct HardScoreGetScore end
    function get_score(sf::HardScore{I}, i::I)::AbstractFloat where {I}
        i == sf.value ? 1.0 : 0.0
    end
end


using PlotUtils
using Test

# TODO: real tests

# ----------------------
# colors

const C = RGBA{Float64}

@testset "colors" begin

    @test plot_color(nothing) == C(0,0,0,0)
    @test plot_color(false) == C(0,0,0,0)
    @test_throws ErrorException plot_color(true)

    @test plot_color(:red) == parse(C, :red)
    @test plot_color("red") == parse(C, "red")
    @test_throws ErrorException plot_color("notacolor")

    @test plot_color(colorant"red") == C(1,0,0,1)

    grad = cgrad()
    @test typeof(grad) == ColorGradient
    @test plot_color(grad) === grad

    grad = cgrad([:red, "blue"])
    @test grad.colors == C[colorant"red", colorant"blue"]
    @test grad.values == collect(range(0,stop=1,length=2))

    grad = cgrad([:red, "blue"], alpha = 0.5)
    @test grad.colors == C[C(1,0,0,0.5), C(0,0,1,0.5)]
    @test grad.values == collect(range(0,stop=1,length=2))

    grad = cgrad([:red,:blue], [0,0.1,1])
    @test grad.colors == C[C(1,0,0), C(0.5,0,0.5), C(0,0,1)]
    @test grad.values == [0,0.1,1]

    cs = plot_color(rand(10))
    @test typeof(cs) == Vector{C}
    @test length(cs) == 10

    cs = plot_color(rand(4,4))
    @test typeof(cs) == Matrix{C}
    @test length(cs) == 16
    @test size(cs) == (4,4)

    cs = plot_color(rand(10), 0.5)
    @test typeof(cs) == Vector{C}
    @test length(cs) == 10
    for c in cs
        @test alpha(c) == 0.5
    end

    cs = plot_color(rand(4,4), 0.5)
    @test typeof(cs) == Matrix{C}
    @test length(cs) == 16
    @test size(cs) == (4,4)
    for c in cs
        @test alpha(c) == 0.5
    end
end

# ----------------------
# gradients

@testset "gradients" begin
    grad = cgrad(:inferno)
    @test length(grad.colors) == 30
    @test length(grad.values) == 30
    @test RGB(grad.colors[1]) == RGB(0.001462, 0.000466, 0.013866)
    @test RGB(grad.colors[end]) == RGB(0.988362, 0.998364, 0.644924)
end

# ----------------------
# ticks

@testset "ticks" begin
    @test optimize_ticks(-1,2) == ([-1.0,0.0,1.0,2.0],-1.0,2.0)

    @testset "small range $x, $(i)ϵ" for x in exp10.(-12:12), i in -5:5
        y = x + i*eps(x)
        x,y = minmax(x,y)
        ticks = PlotUtils.optimize_ticks(x, y)[1]
        @test issorted(ticks)
        @test all(x .<= ticks .<= y)
        # Fails:
        # @test allunique(ticks)
    end
end


using POMCPOW
using Test

using POMDPs
using POMDPModels
using ParticleFilters
using POMDPTesting
using D3Trees
using BeliefUpdaters
using POMDPModelTools

solver = POMCPOWSolver()

pomdp = BabyPOMDP()

test_solver(solver, pomdp, updater=DiscreteUpdater(pomdp))
test_solver(solver, pomdp)

# make sure internal function is type stable
planner = solve(solver, pomdp)
b = initialstate_distribution(pomdp)
B = POMCPOW.belief_type(POMCPOW.POWNodeFilter, typeof(pomdp))
tree = POMCPOWTree{B,Bool,Bool,typeof(b)}(b, 2*planner.solver.tree_queries)
@inferred POMCPOW.simulate(planner, POMCPOW.POWTreeObsNode(tree, 1), true, 10)
# @code_warntype POMCPOW.simulate(planner, POMCPOW.POWTreeObsNode(tree, 1), true, 10)

pomdp = LightDark1D()
solver = POMCPOWSolver(default_action=485)
planner = solve(solver, pomdp)

b = ParticleCollection([LightDark1DState(-1, 0)])
@test @inferred(action(planner, b)) == 485

b = initialstate_distribution(pomdp)
@inferred action(planner, b)

a, info = action_info(planner, b)
# d3t = D3Tree(planner)
@test_throws KeyError d3t = D3Tree(info[:tree])

a, info = action_info(planner, b, tree_in_info=true)
# d3t = D3Tree(planner)
d3t = D3Tree(info[:tree])
# inchrome(d3t)

include("init_node_sr_belief_error.jl")


# import stuff
using Distributed
@everywhere using Pkg
@everywhere Pkg.activate(".")
@everywhere using Statistics
@everywhere using GRASS
@everywhere using SharedArrays
@everywhere using EchelleCCFs
using CSV
using DataFrames
using LaTeXStrings
using LsqFit

# define rms loop function
include(GRASS.moddir * "figures/fig_functions.jl")

# some global stuff
const N = 132
const Nt = 200
const Nloop = 200

# get command line args and output directories
run, plot = parse_args(ARGS)
grassdir, plotdir, datadir = check_plot_dirs()

function main()
    # set up stuff for lines
    lines = [5434.5]
    depths = [0.8]
    resolution = 700000.0
    top = NaN
    contiguous_only=false

    # get angles
    n_inc = 20
    incls = (π/180.0) .* range(0.0, 90.0, length=n_inc)
    ycomp = sin.(incls)
    zcomp = cos.(incls)

    # make pole vectors
    poles = Array{Tuple{Float64, Float64, Float64}, 1}(undef, n_inc)
    for i in eachindex(incls)
        pole = (0.0, ycomp[i], zcomp[i])
        poles[i] = pole
    end

    # allocate shared sharred arrays
    avg_avg_inc = SharedArray{Float64}(n_inc)
    std_avg_inc = SharedArray{Float64}(n_inc)
    avg_rms_inc = SharedArray{Float64}(n_inc)
    std_rms_inc = SharedArray{Float64}(n_inc)

    # loop over inclinations
    @sync @distributed for i in eachindex(poles)
        println("running pole " * string(i) * " of " * string(n_inc))

         # create spec and disk params instances
        spec = SpecParams(lines=lines, depths=depths, resolution=resolution,
                          extrapolate=true, contiguous_only=contiguous_only)
        disk = DiskParams(N=N, Nt=Nt, pole=poles[i])

        # synthesize spectra, get velocities and stats
        avg_avg1, std_avg1, avg_rms1, std_rms1 = spec_loop(spec, disk, Nloop, top=top)
        avg_avg_inc[i] = avg_avg1
        std_avg_inc[i] = std_avg1
        avg_rms_inc[i] = avg_rms1
        std_rms_inc[i] = std_rms1
    end

    # make data frame
    df = DataFrame()
    df[!,:inc] = incls
    df[!,:avg_avg_inc] = avg_avg_inc
    df[!,:std_avg_inc] = std_avg_inc
    df[!,:avg_rms_inc] = avg_rms_inc
    df[!,:std_rms_inc] = std_rms_inc

    # write results to CSV
    fname = datadir * "inclination_" *  string(N) * ".csv"
    CSV.write(fname, df)
    return nothing
end

# run the simulation
if run
    main()
end

# plotting code block
if plot
    # plotting imports
    import PyPlot; plt = PyPlot; mpl = plt.matplotlib; plt.ioff()
    using PyCall; animation = pyimport("matplotlib.animation")
    mpl.style.use(GRASS.moddir * "figures/fig.mplstyle")

    # read in the data
    fname = datadir * "inclination_" *  string(N) * ".csv"
    df = CSV.read(fname, DataFrame)

    # assign to variable names
    ang = df.inc .* (180.0/π)
    avg_avg_inc = df.avg_avg_inc
    std_avg_inc = df.std_avg_inc
    avg_rms_inc = df.avg_rms_inc
    std_rms_inc = df.std_rms_inc

    # get the errors
    err_avg_inc = std_avg_inc ./ sqrt(Nloop)
    err_rms_inc = std_rms_inc ./ sqrt(Nloop)

    # plot the results
    fig = plt.figure()
    ax1 = fig.add_subplot()
    ax1.errorbar(ang, avg_rms_inc, yerr=err_rms_inc, capsize=3.0, color="black", fmt=".")
    ax1.fill_between(ang, avg_rms_inc .- std_rms_inc, avg_rms_inc .+ std_rms_inc, color="tab:blue", alpha=0.3)

    # annotate the axes
    arrowprops = Dict("facecolor"=>"black", "shrink"=>0.05, "width"=>2.0,"headwidth"=>8.0)
    ax1.annotate(L"\textnormal{Pole-on}", xy=(69.8, 0.39), xytext=(0.0,0.388), arrowprops=arrowprops)
    ax1.annotate(L"\textnormal{Equator-on}", xy=(70.0, 0.388))

    # set labels, etc.
    ax1.set_xlabel(L"{\rm Inclination\ (deg)}")
    ax1.set_ylabel(L"{\rm RMS}_{\rm RV}\ {\rm (m s}^{-1})")
    ax1.set_xticks(range(0, 90, length=10))
    ax1.set_ylim(0.375, 0.675)

    # save the figure
    fig.savefig(plotdir * "fig6.pdf")
    plt.clf(); plt.close()
    println(">>> Figure written to: " * plotdir * "fig6.pdf")
end


function regulateperiphery!(system::CVSystem,n::Int64,terms::Vector{Int64})
    for i = 1:length(terms)
        if system.branches.group[terms[i]] == "lower"
            system.branches.term[terms[i]].R[2] = system.cns.R2L[n+2];
            system.branches.term[terms[i]].R[3] = system.cns.R3L[n+2];
            system.branches.term[terms[i]].C[4] = system.cns.C4L[n+2];
            system.branches.term[terms[i]].C[5] = system.cns.C5L[n+2];
            system.branches.term[terms[i]].V0[4] = system.cns.V4L[n+2];
            system.branches.term[terms[i]].V0[5] = system.cns.V5L[n+2];
        else
            system.branches.term[terms[i]].R[2] = system.cns.R2U[n+2];
            system.branches.term[terms[i]].R[3] = system.cns.R3U[n+2];
            system.branches.term[terms[i]].C[4] = system.cns.C4U[n+2];
            system.branches.term[terms[i]].C[5] = system.cns.C5U[n+2];
            system.branches.term[terms[i]].V0[4] = system.cns.V4U[n+2];
            system.branches.term[terms[i]].V0[5] = system.cns.V5U[n+2];
        end
    end
end


"""
    mutable struct DynamicGenerator{
        M <: Machine,
        S <: Shaft,
        A <: AVR,
        TG <: TurbineGov,
        P <: PSS,
    } <: DynamicInjection
        name::String
        ω_ref::Float64
        machine::M
        shaft::S
        avr::A
        prime_mover::TG
        pss::P
        n_states::Int
        states::Vector{Symbol}
        ext::Dict{String, Any}
        internal::InfrastructureSystemsInternal
    end

A dynamic generator is composed by 5 components, namely a Machine, a Shaft, an Automatic Voltage Regulator (AVR),
a Prime Mover (o Turbine Governor) and Power System Stabilizer (PSS). It requires a Static Injection device that is attached to it.

# Arguments
- `name::String`: Name of generator.
- `ω_ref::Float64`: Frequency reference set-point in pu.
- `machine <: Machine`: Machine model for modeling the electro-magnetic phenomena.
- `shaft <: Shaft`: Shaft model for modeling the electro-mechanical phenomena.
- `avr <: AVR`: AVR model of the excitacion system.
- `prime_mover <: TurbineGov`: Prime Mover and Turbine Governor model for mechanical power.
- `pss <: PSS`: Power System Stabilizer model.
- `n_states::Int`: Number of states (will depend on the components).
- `states::Vector{Symbol}`: Vector of states (will depend on the components).
- `ext::Dict{String, Any}`
- `internal::InfrastructureSystemsInternal`: power system internal reference, do not modify
"""
mutable struct DynamicGenerator{
    M <: Machine,
    S <: Shaft,
    A <: AVR,
    TG <: TurbineGov,
    P <: PSS,
} <: DynamicInjection
    name::String
    ω_ref::Float64
    machine::M
    shaft::S
    avr::A
    prime_mover::TG
    pss::P
    n_states::Int
    states::Vector{Symbol}
    ext::Dict{String, Any}
    internal::InfrastructureSystemsInternal
end

function DynamicGenerator(
    name::String,
    ω_ref::Float64,
    machine::M,
    shaft::S,
    avr::A,
    prime_mover::TG,
    pss::P,
    ext::Dict{String, Any} = Dict{String, Any}(),
) where {M <: Machine, S <: Shaft, A <: AVR, TG <: TurbineGov, P <: PSS}
    n_states = _calc_n_states(machine, shaft, avr, prime_mover, pss)
    states = _calc_states(machine, shaft, avr, prime_mover, pss)

    return DynamicGenerator{M, S, A, TG, P}(
        name,
        ω_ref,
        machine,
        shaft,
        avr,
        prime_mover,
        pss,
        n_states,
        states,
        ext,
        InfrastructureSystemsInternal(),
    )
end

function DynamicGenerator(;
    name::String,
    ω_ref::Float64,
    machine::M,
    shaft::S,
    avr::A,
    prime_mover::TG,
    pss::P,
    n_states = _calc_n_states(machine, shaft, avr, prime_mover, pss),
    states = _calc_states(machine, shaft, avr, prime_mover, pss),
    ext::Dict{String, Any} = Dict{String, Any}(),
    internal = InfrastructureSystemsInternal(),
) where {M <: Machine, S <: Shaft, A <: AVR, TG <: TurbineGov, P <: PSS}
    DynamicGenerator(
        name,
        ω_ref,
        machine,
        shaft,
        avr,
        prime_mover,
        pss,
        n_states,
        states,
        ext,
        internal,
    )
end

IS.get_name(device::DynamicGenerator) = device.name
get_states(device::DynamicGenerator) = device.states
get_n_states(device::DynamicGenerator) = device.n_states
get_ω_ref(device::DynamicGenerator) = device.ω_ref
get_machine(device::DynamicGenerator) = device.machine
get_shaft(device::DynamicGenerator) = device.shaft
get_avr(device::DynamicGenerator) = device.avr
get_prime_mover(device::DynamicGenerator) = device.prime_mover
get_pss(device::DynamicGenerator) = device.pss
get_ext(device::DynamicGenerator) = device.ext
get_internal(device::DynamicGenerator) = device.internal
get_V_ref(value::DynamicGenerator) = get_V_ref(get_avr(value))
get_P_ref(value::DynamicGenerator) = get_P_ref(get_prime_mover(value))

function _calc_n_states(machine, shaft, avr, prime_mover, pss)
    return get_n_states(machine) +
           get_n_states(shaft) +
           get_n_states(avr) +
           get_n_states(prime_mover) +
           get_n_states(pss)
end

function _calc_states(machine, shaft, avr, prime_mover, pss)
    return vcat(
        get_states(machine),
        get_states(shaft),
        get_states(avr),
        get_states(prime_mover),
        get_states(pss),
    )
end


#=

Model definition for *SSE models

Ignacio Quintero Mächler

t(-_-t)

27 05 2019

=#

"""
    define_mod(esse_mod::String, x::Array{Float64,1}, y::Array{Float64,1}, k::Int64)

Defines ESSE model.
"""
function define_mod(esse_mod::String,
                    k       ::Int64,
                    af      ::Function,
                    md      ::Bool)

  if occursin(r"^[s|S][A-za-z]*", esse_mod)         # if speciation
    mod_ode = md ? make_esse_s(k, af, md) : make_esse_s(k, af)
    npars   = 2k + k*k
    pardic  = build_par_names(k, true)
    ws      = true
    printstyled("running speciation ESSE model \n", color=:green)

  elseif occursin(r"^[e|E][A-za-z]*", esse_mod)     # if extinction
    mod_ode = md ? make_esse_e(k, af, md) : make_esse_e(k, af)
    npars   = (2k + k*k)::Int64
    pardic  = build_par_names(k, true)
    ws      = false

    printstyled("running extinction ESSE model \n", color=:green)

  elseif occursin(r"^[t|T|r|R|q|Q][A-za-z]*", esse_mod) # if transition
    mod_ode = md ? make_esse_q(k, af, md) : make_esse_q(k, af)
    npars   = 2k*k::Int64
    pardic  = build_par_names(k, false)
    ws      = false

    printstyled("running transition ESSE model \n", color=:green)

  else
    error("esse_mod does not match any of the alternatives:
          speciation, extinction or transition")
  end

  return mod_ode, npars, pardic, md, ws

end

"""
    define_mod(cov_mod::String,
               k            ::Int64,
               h            ::Int64,
               ny           ::Int64)

Defines EGeoHiSSE model for `k` areas, `h` hidden states and `ny` covariates.
"""
function define_mod(cov_mod::NTuple{N,String},
                    k      ::Int64,
                    h      ::Int64,
                    ny     ::Int64) where {N}

  model = [false, false, false]

  for m in cov_mod
    # if speciation
    if occursin(r"^[s|S][A-za-z]*", m)
      model[1] = true
    end
    # if extinction
    if occursin(r"^[e|E][A-za-z]*", m)
      model[2] = true
    end
    # if dispersal
    if occursin(r"^[t|T|r|R|q|Q][A-za-z]*", m)
      model[3] = true
    end
  end

  mexp = "$(model[1] ? "speciation," : "")$(model[2] ? "extinction," : "")$(model[3] ? "transition," : "")"
  mexp = replace(mexp, "," => ", ")
  mexp = mexp[1:(end-2)]

  printstyled("running $mexp EGeoHiSSE model with:
    $k single areas
    $h hidden states
    $ny covariates \n", color=:green)

  return tuple(model...)
end


# MIT License
#
# Copyright (c) 2018 Martin Biel
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

# Objective #
# ========================== #
struct FunctionizeDecisionObjectiveBridge{T} <: MOIB.Objective.AbstractBridge end

function MOIB.Objective.bridge_objective(::Type{FunctionizeDecisionObjectiveBridge{T}}, model::MOI.ModelLike,
                                         f::SingleDecision) where T
    F = AffineDecisionFunction{T}
    MOI.set(model, MOI.ObjectiveFunction{F}(), convert(F, f))
    return FunctionizeDecisionObjectiveBridge{T}()
end

function MOIB.Objective.supports_objective_function(
    ::Type{<:FunctionizeDecisionObjectiveBridge}, ::Type{SingleDecision})
    return true
end
MOIB.added_constrained_variable_types(::Type{<:FunctionizeDecisionObjectiveBridge}) = Tuple{DataType}[]
function MOIB.added_constraint_types(::Type{<:FunctionizeDecisionObjectiveBridge})
    return Tuple{DataType, DataType}[]
end
function MOIB.set_objective_function_type(::Type{FunctionizeDecisionObjectiveBridge{T}}) where T
    return AffineDecisionFunction{T}
end

# Attributes, Bridge acting as a model
function MOI.get(bridge::FunctionizeDecisionObjectiveBridge, ::MOI.NumberOfVariables)
    return 0
end
function MOI.get(bridge::FunctionizeDecisionObjectiveBridge, ::MOI.ListOfVariableIndices)
    return MOI.VariableIndex[]
end

# No variables or constraints are created in this bridge so there is nothing to
# delete.
function MOI.delete(model::MOI.ModelLike, bridge::FunctionizeDecisionObjectiveBridge) end

function MOI.set(::MOI.ModelLike, ::MOI.ObjectiveSense,
                 ::FunctionizeDecisionObjectiveBridge, ::MOI.OptimizationSense)
    # `FunctionizeDecisionObjectiveBridge` is sense agnostic, therefore, we don't need to change
    # anything.
    return nothing
end
function MOI.get(model::MOI.ModelLike,
                 attr::MOIB.ObjectiveFunctionValue{SingleDecision},
                 bridge::FunctionizeDecisionObjectiveBridge{T}) where T
    F = AffineDecisionFunction{T}
    return MOI.get(model, MOIB.ObjectiveFunctionValue{F}(attr.result_index))
end
function MOI.get(model::MOI.ModelLike, attr::MOI.ObjectiveFunction{SingleDecision},
                 bridge::FunctionizeDecisionObjectiveBridge{T}) where T
    F = AffineDecisionFunction{T}
    func = MOI.get(model, MOI.ObjectiveFunction{F}())
    return convert(SingleDecision, func)
end


module PandaModels
import JuMP
import InfrastructureModels
import PowerModels
import PowerModels: ids, ref, var, con, sol, nw_id_default
import Memento
import JSON
import Cbc
import Ipopt
import Juniper
const _IM = InfrastructureModels
const _PM = PowerModels
const _PdM = PandaModels
const LOGGER = Memento.getlogger(PowerModels)

export run_powermodels_pf,
    run_powermodels_opf,
    run_powermodels_ots,
    run_powermodels_tnep,
    run_pandamodels_vstab,
    run_pandamodels_multi_vstab,
    run_pandamodels_qflex,
    run_pandamodels_multi_qflex,
    run_pandamodels_vstab_test,
    run_pandamodels_qflex_test

include("input/pp_to_pm.jl")
include("input/tools.jl")
include("models/vstab.jl")
include("models/qflex.jl")
include("models/call_pandamodels.jl")
include("models/call_powermodels.jl")
include("models/run_pm_vstab_dev.jl")
include("models/run_pm_qflex_dev.jl")

end


struct HessianNull <: Hessian end
struct HessianD21{H1,H2} <: Hessian
    h1::H1
    h2::H2
    ref::MyRef{Float64}
    ref1::MyRef{Float64}
    ref2::MyRef{Float64}
end
struct Hessian11{F,H1,H11} <: Hessian
    h1::H1
    h11::H11
    fref::MyRef{Float64}
    ref::MyRef{Float64}

    function Hessian11(e::Expression1{F,E},d, indexer = nothing) where {F,E}
        h1 = Hessian(e.e1,d.d1,indexer)
        h11= Hessian(d.d1,d.d1,indexer)
        return new{F,typeof(h1),typeof(h11)}(h1,h11,ref(e.e1),ref(d))
    end
end
struct Hessian11F1{F,H1,H11,R} <: Hessian
    a::R
    h1::H1
    h11::H11
    fref::MyRef{Float64}
    ref::MyRef{Float64}
    function Hessian11F1(e::Expression2{F,E1,E2}, d,indexer = nothing) where {F,E1 <: Real,E2}
        h1 = Hessian(e.e2,d.d1,indexer)
        h11= Hessian(d.d1,d.d1,indexer)
        return new{F,typeof(h1),typeof(h11),typeof(e.e1)}(e.e1,h1,h11,ref(e.e2),ref(d))
    end
end
struct Hessian11F2{F,H1,H11,R} <: Hessian
    a::R
    h1::H1
    h11::H11
    fref::MyRef{Float64}
    ref::MyRef{Float64}
    function Hessian11F2(e::Expression2{F,E1,E2}, d,indexer = nothing) where {F,E1,E2 <: Real}
        h1 = Hessian(e.e1,d.d1,indexer)
        h11= Hessian(d.d1,d.d1,indexer)
        return new{F,typeof(h1),typeof(h11),typeof(e.e2)}(e.e2,h1,h11,ref(e.e1),ref(d))
    end
end
struct Hessian02{H11,H12,H21,H22} <: Hessian
    h11::H11
    h12::H12
    h21::H21
    h22::H22
    ref11::MyRef{Float64}
    ref12::MyRef{Float64}
    ref21::MyRef{Float64}
    ref22::MyRef{Float64}
end
struct Hessian22{F,H1,H2,H11,H12,H21,H22} <: Hessian
    h1::H1
    h2::H2
    h11::H11
    h12::H12
    h21::H21
    h22::H22
    fref1::MyRef{Float64}
    fref2::MyRef{Float64}
    ref1::MyRef{Float64}
    ref2::MyRef{Float64}

    function Hessian22(e::Expression2{F,E1,E2},d,indexer = nothing) where {F,E1,E2}
        h1 = Hessian(e.e1,d.d1,indexer)
        h2 = Hessian(e.e2,d.d2,indexer)
        h11= Hessian(d.d1,d.d1,indexer)
        h12= Hessian(d.d1,d.d2,indexer)
        h21= Hessian(d.d2,d.d1,indexer)
        h22= Hessian(d.d2,d.d2,indexer)
        new{F,typeof(h1),typeof(h2),typeof(h11),typeof(h12),typeof(h21),typeof(h22)}(h1,h2,h11,h12,h21,h22,ref(e.e1),ref(e.e2),ref1(d),ref2(d))
    end
end
# struct Hessian20{H1,H2} <: Hessian
#     h1::H1
#     h2::H2
#     ref1::MyRef{Float64}
#     ref2::MyRef{Float64}
# end
struct HessianD00 <: Hessian
    index1::Int
    index2::Int
    islower::Bool
end
struct HessianD00S <: Hessian
    index::Int
    islower::Bool
end
struct HessianD20{H1,H2} <: Hessian
    h1::H1
    h2::H2
    ref1::MyRef{Float64}
    ref2::MyRef{Float64}
end
struct HessianD10{H} <: Hessian
    h::H
    ref::MyRef{Float64}
end
struct HessianD11{H} <: Hessian
    h::H
    ref1::MyRef{Float64}
    ref2::MyRef{Float64}
end
struct Hessian22a{H1,H2} <: Hessian
    h1::H1
    h2::H2
    ref1::MyRef{Float64}
    ref2::MyRef{Float64}
end
struct Hessian22m{H1,H2,H12,H21} <: Hessian
    h1::H1
    h2::H2
    h12::H12
    h21::H21
    ref1::MyRef{Float64}
    ref2::MyRef{Float64}
end
struct Hessian11a{H} <: Hessian
    h1::H
    ref::MyRef{Float64}
end
struct HessianSum{I,H} <: Hessian
    inner::I
    hs::Vector{H}
end

const HESSIAN_NULL = HessianNull()

@inline function (h::HessianSum{I,H})(z,x,p=nothing,h0=1) where {I,H}
    inner(h)(z,x,p,h0)
    @simd for i in eachindex(h.hs)
        @inbounds h.hs[i](z,x,p,h0)
    end
end
@inline function (h::HessianSum{Nothing,H})(z,x,p=nothing,h0=1) where H
    @simd for i in eachindex(h.hs)
        @inbounds h.hs[i](z,x,p,h0)
    end
end

@inline (::HessianNull)(z,x,p=nothing,h0=1) = nothing
@inline function (h::HessianD00)(z,x,p=nothing,h0 = 1)
    islower(h) && @inbounds z[index1(h)::Int,index2(h)::Int] += h0
    return
end
@inline function (h::HessianD00S)(z,x,p=nothing,h0 = 1)
    islower(h) && @inbounds z[index(h)::Int] += h0
    return
end
@inline function (h::HessianD10{H})(z,x,p=nothing,h0 = 1) where H
    h.h(z,x,p,h0*refval(h))
    return
end
@inline function (h::HessianD11{H})(z,x,p=nothing,h0 = 1) where H
    h.h(z,x,p,h0*refval1(h)*refval2(h))
    return
end
@inline function (h::HessianD21{H1,H2})(z,x,p=nothing,h0=1) where {H1,H2}
    h.h1(z,x,p,h0*refval(h)*refval1(h))
    h.h2(z,x,p,h0*refval(h)*refval2(h))
    return
end
@inline function (h::HessianD20{H1,H2})(z,x,p=nothing,h0=1) where {H1,H2}
    h.h1(z,x,p,h0*refval1(h))
    h.h2(z,x,p,h0*refval2(h))
end
@inline function (h::Hessian02{H11,H12,H21,H22})(z,x,p=nothing,h0=1) where {H11,H12,H21,H22}
    h.h11(z,x,p,h0*refval11(h)*refval21(h))
    h.h12(z,x,p,h0*refval11(h)*refval22(h))
    h.h21(z,x,p,h0*refval12(h)*refval21(h))
    h.h22(z,x,p,h0*refval12(h)*refval22(h))
    return
end
@inline function (h::Hessian11a{H})(z,x,p=nothing,h0=1) where {H}
    h.h1(z,x,p,h0*refval(h))
    return
end
@inline function (h::Hessian22a{H1,H2})(z,x,p=nothing,h0=1) where {H1,H2}
    h.h1(z,x,p,h0*refval1(h))
    h.h2(z,x,p,h0*refval2(h))
    return
end
@inline function (h::Hessian22m{H1,H2,H12})(z,x,p=nothing,h0=1) where {H1,H2,H12}
    h.h1(z,x,p,h0*refval1(h))
    h.h2(z,x,p,h0*refval2(h))
    h.h12(z,x,p,h0)
    h.h21(z,x,p,h0)
    return
end

Hessian(e::ExpressionSum{E,I1},d::GradientSum{D,I2},indexer = nothing) where {E,D,I1,I2} = HessianSum(Hessian(inner(e),inner(d),indexer),[Hessian(e,d,indexer) for (e,d) in zip(e.es,d.ds)])
Hessian(e::ExpressionSum{E,Nothing},d::GradientSum{D,Nothing},indexer = nothing) where {E,D} = HessianSum(nothing,[Hessian(e,d,indexer) for (e,d) in zip(e.es,d.ds)])
Hessian(e::Variable,::G,indexer) where G <: Gradient = HESSIAN_NULL
Hessian(e::Parameter,::G,indexer) where G <: Gradient = HESSIAN_NULL
Hessian(e::Constant,::G,indexer) where G <: Gradient = HESSIAN_NULL
Hessian(d1::G,d2::GradientNull,indexer) where G <: Gradient = HESSIAN_NULL
Hessian(d1::GradientNull,d2::G,indexer) where G <: Gradient = HESSIAN_NULL
Hessian(d1::GradientNull,d2::GradientNull,indexer) = HESSIAN_NULL
Hessian(d1::Gradient0,d2::Gradient0,indexer)= HessianD00S(index(d1)>=index(d2) ? set_indexer!(indexer,index(d1),index(d2)) : 0,index(d1) >= index(d2))
Hessian(d1::Gradient0,d2::Gradient0,::Nothing) = HessianD00(index(d1),index(d2),index(d1) >= index(d2))
Hessian(d1::Gradient0,d2::G1, indexer = nothing) where G1 <: Union{Gradient1,Gradient2F1,Gradient2F2} = HessianD10(Hessian(d1,d2.d1,indexer),ref(d2))
Hessian(d1::G1,d2::Gradient0, indexer = nothing) where G1 <: Union{Gradient1,Gradient2F1,Gradient2F2} = HessianD10(Hessian(d1.d1,d2,indexer),ref(d1))
Hessian(d1::G1,d2::G2, indexer = nothing) where {G1 <: Union{Gradient1,Gradient2F1,Gradient2F2}, G2 <: Union{Gradient1,Gradient2F1,Gradient2F2}}= HessianD11(Hessian(d1.d1,d2.d1,indexer),ref(d1),ref(d2))
Hessian(d1::G1,d2::G2, indexer = nothing) where {G1 <: Union{Gradient1,Gradient2F1,Gradient2F2}, G2 <: Gradient2} = HessianD21(Hessian(d1.d1,d2.d1,indexer),Hessian(d1.d1,d2.d2,indexer),ref(d1),ref1(d2),ref2(d2))
Hessian(d1::G1,d2::G2, indexer = nothing) where {G1 <: Gradient2, G2 <: Union{Gradient1,Gradient2F1,Gradient2F2}} = HessianD21(Hessian(d1.d1,d2.d1,indexer),Hessian(d1.d2,d2.d1,indexer),ref(d2),ref1(d1),ref2(d1))
Hessian(d1::Gradient0,d2::Gradient2{F,F1,F2}, indexer = nothing) where {F,F1,F2} = HessianD20(Hessian(d1,d2.d1,indexer),Hessian(d1,d2.d2,indexer),ref1(d2),ref2(d2))
Hessian(d1::Gradient2{F,F1,F2},d2::Gradient0, indexer = nothing) where {F,F1,F2} = HessianD20(Hessian(d1.d1,d2,indexer),Hessian(d1.d2,d2,indexer),ref1(d1),ref2(d1))
Hessian(d1::Gradient2{F,F1,F2} where {F,F1,F2},d2::Gradient2{F,F1,F2} where {F,F1,F2}, indexer = nothing) = Hessian02(Hessian(d1.d1,d2.d1,indexer),Hessian(d1.d1,d2.d2,indexer),Hessian(d1.d2,d2.d1,indexer),Hessian(d1.d2,d2.d2,indexer),ref1(d1),ref2(d1),ref1(d2),ref2(d2))
Hessian(e::Expression1{F,E},d, indexer = nothing) where {F,E} = Hessian11(e,d,indexer)
Hessian(e::Expression2{F,E1,E2},d,indexer = nothing) where {F,E1,E2} = Hessian22(e,d,indexer)
Hessian(e::Expression2{F,E1,E2}, d,indexer = nothing) where {F,E1 <: Real,E2} = Hessian11F1(e,d,indexer)
Hessian(e::Expression2{F,E1,E2}, d,indexer = nothing) where {F,E1,E2 <: Real} = Hessian11F2(e,d,indexer)
Hessian(e::Expression2{typeof(*),E1,E2},d,indexer = nothing) where {E1,E2} = Hessian22m(Hessian(e.e1,d.d1,indexer),Hessian(e.e2,d.d2,indexer),Hessian(d.d1,d.d2,indexer),Hessian(d.d2,d.d1,indexer),ref1(d),ref2(d))
Hessian(e::Expression2{F,E1,E2},d,indexer = nothing) where {F<:Union{typeof(+),typeof(-)},E1,E2} = Hessian22a(Hessian(e.e1,d.d1,indexer),Hessian(e.e2,d.d2,indexer),ref1(d),ref2(d))
Hessian(e::Expression2{F,E1,E2}, d,indexer = nothing) where {F<:Union{typeof(+),typeof(-),typeof(*)},E1 <: Real,E2 <: Expression} = Hessian11a(Hessian(e.e2,d.d1,indexer),ref(d))
Hessian(e::Expression2{F,E1,E2}, d,indexer = nothing) where {F<:Union{typeof(+),typeof(-),typeof(*),typeof(/)},E1 <: Expression,E2 <: Real} = Hessian11a(Hessian(e.e1,d.d1,indexer),ref(d))

# performance killer ---------------
function Hessian(d1::GradientSum{D1,I1},d2::GradientSum{D2,I2},indexer = nothing) where {D1,D2,I1,I2}
    @warn "This operation is expensive"
    hinner = Hessian(inner(d1),d2,indexer)
    hs = [Hessian(d,d2,indexer) for d in d1.ds]
    @inline function (z,x,p=nothing,h0=1)
        hinner(z,x,p,h0)
        @simd for i in eachindex(hs)
            @inbounds hs[i](z,x,p,h0)
        end
    end
end
function Hessian(d1::GradientSum{D1,Nothing},d2::GradientSum{D2,I2},indexer = nothing) where {D1,D2,I2}
    @warn "This operation is expensive"
    hs = [Hessian(d,d2,indexer) for d in d1.ds]
    @inline function (z,x,p=nothing,h0=1)
        @simd for i in eachindex(hs)
            @inbounds hs[i](z,x,p,h0)
        end
    end
end
function Hessian(d1::GradientSum{D1,I1},d2::GradientSum{D2,Nothing},indexer = nothing) where {D1,D2,I1}
    @warn "This operation is expensive"
    hinner = Hessian(inner(d1),d2,indexer)
    hs = [Hessian(d,d2,indexer) for d in d1.ds]
    @inline function (z,x,p=nothing,h0=1)
        hinner(z,x,p,h0)
        @simd for i in eachindex(hs)
            @inbounds hs[i](z,x,p,h0)
        end
    end
end
function Hessian(d1::GradientSum{D1,Nothing},d2::GradientSum{D2,Nothing},indexer = nothing) where {D1,D2}
    @warn "This operation is expensive"
    hs = [Hessian(d,d2,indexer) for d in d1.ds]
    @inline function (z,x,p=nothing,h0=1)
        @simd for i in eachindex(hs)
            @inbounds hs[i](z,x,p,h0)
        end
    end
end
function Hessian(d1::GradientSum{D1,I1},d2::G,indexer = nothing) where {D1,D2,I1,I2,G <: Gradient}
    @warn "This operation is expensive"
    hinner = Hessian(inner(d1),d2,indexer)
    hs = [Hessian(d,d2,indexer) for d in d1.ds]
    @inline function (z,x,p=nothing,h0=1)
        hinner(z,x,p,h0)
        @simd for i in eachindex(hs)
            @inbounds hs[i](z,x,p,h0)
        end
    end
end
function Hessian(d1::GradientSum{D1,Nothing},d2::G,indexer = nothing) where {D1,D2,I2,G <: Gradient}
    @warn "This operation is expensive"
    hs = [Hessian(d,d2,indexer) for d in d1.ds]
    @inline function (z,x,p=nothing,h0=1)
        @simd for i in eachindex(hs)
            @inbounds hs[i](z,x,p,h0)
        end
    end
end
function Hessian(d1::G,d2::GradientSum{D2,I2},indexer = nothing) where {G <: Gradient,D2,I2}
    @warn "This operation is expensive"
    hinner = Hessian(d1,inner(d2),indexer)
    hs = [Hessian(d1,d,indexer) for d in d2.ds]
    @inline function (z,x,p=nothing,h0=1)
        hinner(z,x,p,h0)
        @simd for i in eachindex(hs)
            @inbounds hs[i](z,x,p,h0)
        end
    end
end
function Hessian(d1::G,d2::GradientSum{D2,Nothing},indexer = nothing) where {G <: Gradient, D2}
    @warn "This operation is expensive"
    hs = [Hessian(d1,d,indexer) for d in d2.ds]
    @inline function (z,x,p=nothing,h0=1)
        @simd for i in eachindex(hs)
            @inbounds hs[i](z,x,p,h0)
        end
    end
end

islower(h) = h.islower


module ODEs

using ..ZeroProblems: Var, AbstractZeroSubproblem

# TODO: General question about Symbols or Strings for names (or a union??)

#--- General ODE problems

# Need to make this interoperable with DifferentialEquations.jl (and any other packages...)

struct ODE{IP, F, DFDX, DFDP}
	name::Symbol
	f::F
	dfdx::DFDX
	dfdp::DFDP
	pnames::Vector{Symbol}
end

function ODE(f; inplace=true, name=:ode, dfdx=nothing, dfdp=nothing, pnames=Symbol[])
	# TODO: default to inplace=nothing and check for methods with the correct number of arguments
	if pnames isa Vector{Symbol}
		_pnames = pnames
	else
		_pnames = [Symbol(p) for p in pnames]
	end
	return ODE{inplace, typeof(f), typeof(dfdx), typeof(dfdp)}(name, f, dfdx, dfdp, _pnames)
end

#--- Equilibrium problems

struct Equilibrium{O, T} <: AbstractZeroSubproblem
	name::Symbol
	deps::Vector{Var}
	ode::O
	x0::Vector{T}
	p0::Vector{T}
end

function Equilibrium(ode, x0, p0; name=:ep)
	xdim = length(x0)
	pdim = length(p0)
	u = Var(name, xdim + pdim)
	x = Var(:x, xdim, u, 0)
	p = Var(:p, pdim, u, -pdim)
	return Equilibrium(name, )
end

end  # end module


"""
    initialize_sbm_gwf_model(config::Config)

Initial part of the sbm_gwf model concept. The model contains:
    - the vertical SBM concept
    - the following lateral components:
        - 1-D kinematic wave for river flow
        - 1-D kinematic wave for overland flow
        - unconfined aquifer with groundwater flow in four directions (adjacent cells)

The unconfined aquifer contains a recharge, river and a drain (optional) boundary.

The initial part reads the input settings and data as defined in the Config object.
Will return a Model that is ready to run.
"""
function initialize_sbm_gwf_model(config::Config)

    # unpack the paths to the NetCDF files
    tomldir = dirname(config)
    static_path = joinpath(tomldir, config.input.path_static)
    cyclic_path = joinpath(tomldir, config.input.path_static)
    dynamic_path = joinpath(tomldir, config.input.path_forcing)
    instate_path = joinpath(tomldir, config.state.path_input)
    output_path = joinpath(tomldir, config.output.path)

    Δt = Second(config.timestepsecs)
    sizeinmetres = get(config.model, "sizeinmetres", false)
    reinit = get(config.model, "reinit", true)
    do_snow = get(config.model, "snow", false)
    do_reservoirs = get(config.model, "reservoirs", false)
    do_lakes = get(config.model, "lakes", false)
    do_drains = get(config.model, "drains", false)
    do_constanthead = get(config.model, "constanthead", false)

    kw_river_tstep = get(config.model, "kw_river_tstep", 0)
    kw_land_tstep = get(config.model, "kw_land_tstep", 0)

    nc = NCDataset(static_path)
    dims = dimnames(nc[param(config, "input.subcatchment")])

    # There is no need to permute the dimensions of the data, since the active indices are
    # correctly calculated in both ways.
    # The dimension order only needs to be known for interpreting the LDD directions
    # and creating the coordinate maps.
    dims_xy = dims[2] in ("y", "lat")

    subcatch_2d = ncread(nc, param(config, "input.subcatchment"); allow_missing = true)
    # indices based on catchment
    inds, rev_inds = active_indices(subcatch_2d, missing)
    n = length(inds)
    modelsize_2d = size(subcatch_2d)

    river_2d = ncread(nc, param(config, "input.river_location"); type = Bool, fill = false)
    river = river_2d[inds]
    riverwidth_2d =
        ncread(nc, param(config, "input.lateral.river.width"); type = Float64, fill = 0)
    riverwidth = riverwidth_2d[inds]
    riverlength_2d =
        ncread(nc, param(config, "input.lateral.river.length"); type = Float64, fill = 0)
    riverlength = riverlength_2d[inds]

    # read x, y coordinates and calculate cell length [m]
    y_nc = "y" in keys(nc.dim) ? ncread(nc, "y") : ncread(nc, "lat")
    x_nc = "x" in keys(nc.dim) ? ncread(nc, "x") : ncread(nc, "lon")
    if dims_xy
        y = permutedims(repeat(y_nc, outer = (1, length(x_nc))))[inds]
    else
        y = repeat(y_nc, outer = (1, length(x_nc)))[inds]
    end
    cellength = abs(mean(diff(x_nc)))

    xl = fill(mv, n)
    yl = fill(mv, n)
    riverfrac = fill(mv, n)

    for i = 1:n
        xl[i] = sizeinmetres ? cellength : lattometres(y[i])[1] * cellength
        yl[i] = sizeinmetres ? cellength : lattometres(y[i])[2] * cellength
        riverfrac[i] =
            river[i] ? min((riverlength[i] * riverwidth[i]) / (xl[i] * yl[i]), 1.0) : 0.0
    end

    # initialize vertical SBM concept
    sbm = initialize_sbm(nc, config, riverfrac, xl, yl, inds)

    inds_riv, rev_inds_riv = active_indices(river_2d, 0)
    nriv = length(inds_riv)

    # reservoirs
    pits = zeros(Bool, modelsize_2d)
    if do_reservoirs
        reservoirs, resindex, reservoir, pits = initialize_simple_reservoir(config, nc, inds_riv, nriv, pits)
    else
        reservoir = ()
    end

    # lakes
    if do_lakes
        lakes, lakeindex, lake, pits = initialize_natural_lake(config, nc, inds_riv, nriv, pits)
    else
        lake = ()
    end

    # overland flow (kinematic wave)
    βₗ = ncread(nc, param(config, "input.lateral.land.slope"); sel = inds, type = Float64)
    clamp!(βₗ, 0.00001, Inf)
    ldd_2d = ncread(nc, param(config, "input.ldd"); allow_missing = true)
    ldd = ldd_2d[inds]
    dl = fill(mv, n)
    dw = fill(mv, n)
    sw = fill(mv, n)

    for i = 1:n
        dl[i] = detdrainlength(ldd[i], xl[i], yl[i])
        dw[i] = (xl[i] * yl[i])/dl[i]
        sw[i] = river[i] ? max(dw[i] - riverwidth[i], 0.0) : dw[i]
    end

    n_land = ncread(
        nc,
        param(config, "input.lateral.land.n", nothing);
        sel = inds,
        defaults = 0.072,
        type = Float64,
    )

    olf = SurfaceFlow(
        sl = βₗ,
        n = n_land,
        dl = dl,
        Δt = tosecond(Δt),
        its = kw_land_tstep > 0 ? Int(cld(tosecond(Δt), kw_land_tstep)) : kw_land_tstep,
        width = sw,
        wb_pit = pits[inds],
    )

    pcr_dir = dims_xy ? permute_indices(pcrdir) : pcrdir
    graph = flowgraph(ldd, inds, pcr_dir)

    # river flow (kinematic wave)
    riverslope = ncread(
        nc,
        param(config, "input.lateral.river.slope");
        sel = inds_riv,
        type = Float64,
    )
    clamp!(riverslope, 0.00001, Inf)
    riverlength = riverlength_2d[inds_riv]
    riverwidth = riverwidth_2d[inds_riv]
    n_river = ncread(
        nc,
        param(config, "input.lateral.river.n", nothing);
        sel = inds_riv,
        defaults = 0.036,
        type = Float64,
    )
    ldd_riv = ldd_2d[inds_riv]
    graph_riv = flowgraph(ldd_riv, inds_riv, pcr_dir)

    # the indices of the river cells in the land(+river) cell vector
    index_river = filter(i -> !isequal(river[i], 0), 1:n)
    frac_toriver = fraction_runoff_toriver(graph, ldd, index_river, βₗ, n)

    rf = SurfaceFlow(
        sl = riverslope,
        n = n_river,
        dl = riverlength,
        Δt = tosecond(Δt),
        its = kw_river_tstep > 0 ? ceil(Int(tosecond(Δt) / kw_river_tstep)) : kw_river_tstep,
        width = riverwidth,
        reservoir_index = do_reservoirs ? resindex : fill(0, nriv),
        lake_index = do_lakes ? lakeindex : fill(0, nriv),
        reservoir = do_reservoirs ? reservoirs : nothing,
        lake = do_lakes ? lakes : nothing,
        rivercells = river,
    )

    # unconfined aquifer
    if do_constanthead
        constanthead = ncread(
            nc,
            param(config, "input.lateral.subsurface.constant_head", nothing);
            sel = inds,
            type = Float64,
            fill = mv,
        )
        index_constanthead = filter(i -> !isequal(constanthead[i], mv), 1:n)
        constant_head = ConstantHead(constanthead[index_constanthead],
                                    index_constanthead,)

    else
        constant_head = ConstantHead[]
    end

    conductivity = ncread(
        nc,
        param(config, "input.lateral.subsurface.conductivity", nothing);
        sel = inds,
        type = Float64,
    )
    specific_yield = ncread(
        nc,
        param(config, "input.lateral.subsurface.specific_yield", nothing);
        sel = inds,
        type = Float64,
    )

    connectivity = Connectivity(inds, rev_inds, sbm.xl, sbm.yl)
    initial_head = sbm.altitude .- 0.10 # cold state for groundwater head
    initial_head[index_river] = sbm.altitude[index_river]

    if do_constanthead
        initial_head[constant_head.index] = constant_head.head
    end

    aquifer = UnconfinedAquifer(
        initial_head,
        conductivity,
        sbm.altitude,
        sbm.altitude .- sbm.soilthickness ./ 1000.0,
        xl .* yl,
        specific_yield,
        fill(0.0, connectivity.nconnection),  # conductance
    )

    # reset zi and satwaterdepth with groundwater head from unconfined aquifer
    sbm.zi .= (sbm.altitude .- initial_head) .* 1000.0
    sbm.satwaterdepth .= (sbm.soilthickness .- sbm.zi) .* sbm.θₑ

    # river boundary of unconfined aquifer
    infiltration_conductance = ncread(
        nc,
        param(config, "input.lateral.subsurface.infiltration_conductance", nothing);
        sel = inds_riv,
        type = Float64,
    )
    exfiltration_conductance = ncread(
        nc,
        param(config, "input.lateral.subsurface.exfiltration_conductance", nothing);
        sel = inds_riv,
        type = Float64,
    )
    river_bottom = ncread(
        nc,
        param(config, "input.lateral.subsurface.river_bottom", nothing);
        sel = inds_riv,
        type = Float64,
    )

    river_flux = fill(mv,nriv)
    river_stage = fill(mv,nriv)
    river = River(
        river_stage,
        infiltration_conductance,
        exfiltration_conductance,
        river_bottom,
        river_flux,
        index_river,
    )

    # drain boundary of unconfined aquifer (optional)
    if do_drains
        drain = ncread(
            nc,
            param(config, "input.lateral.subsurface.drain", nothing);
            sel = inds,
            type = Bool,
        )
        drain_elevation = ncread(
            nc,
            param(config, "input.lateral.subsurface.drain_elevation", nothing);
            sel = inds,
            type = Float64,
            fill = mv,
        )
        drain_conductance = ncread(
            nc,
            param(config, "input.lateral.subsurface.drain_conductance", nothing);
            sel = inds,
            type = Float64,
            fill = mv,
        )
        index_drain = filter(i -> !isequal(drain[i], 0), 1:n)
        drain_flux = fill(mv,length(index_drain))
        drain = Drainage(
            drain_elevation[index_drain],
            drain_conductance[index_drain],
            drain_flux,
            index_drain,
        )
    else
        drain = Drainage[]
    end

    # recharge boundary of unconfined aquifer
    r = fill(mv,n)
    recharge = Recharge(r, fill(0.0, n), collect(1:n))

    gwf = GroundwaterFlow(
        aquifer,
        connectivity,
        constant_head,
        AquiferBoundaryCondition[recharge, river, drain],
    )

    state_ncnames = ncnames(config.state)

    reader = prepare_reader(dynamic_path, cyclic_path, config)

    modelmap = (vertical = sbm,
                lateral = (subsurface = (flow = gwf, recharge = gwf.boundaries[1], river = gwf.boundaries[2], drain = gwf.boundaries[3]),
                land = olf,
                river = rf))
    indices_reverse = (
        land = rev_inds,
        river = rev_inds_riv,
        reservoir = isempty(reservoir) ? nothing : reservoir.reverse_indices,
        lake = isempty(lake) ? nothing : lake.reverse_indices,
    )
    writer = prepare_writer(
        config,
        reader,
        output_path,
        modelmap,
        state_ncnames,
        indices_reverse,
        x_nc,
        y_nc,
        dims_xy,
        nc,
        maxlayers = sbm.maxlayers,
    )

    # for each domain save the directed acyclic graph, the traversion order,
    # and the indices that map it back to the two dimensional grid
    # for reservoirs and lakes this is information is also available (except the graph)
    # from the initialization functions
    land = (
        graph = graph,
        order = topological_sort_by_dfs(graph),
        indices = inds,
        reverse_indices = rev_inds,
    )
    river = (
        graph = graph_riv,
        order = topological_sort_by_dfs(graph_riv),
        indices = inds_riv,
        reverse_indices = rev_inds_riv,
    )

    model = Model(
        config,
        (; land, river, reservoir, lake, index_river, frac_toriver),
        (subsurface = (flow = gwf, recharge = gwf.boundaries[1], river = gwf.boundaries[2], drain = gwf.boundaries[3]),
        land = olf,
        river = rf),
        sbm,
        Clock(config.starttime, 1, Δt),
        reader,
        writer,
    )

    # read and set states in model object if reinit=false
    if reinit  == false
        set_states(
            instate_path,
            model,
            state_ncnames,
            type = Float64
        )
    end

    # make sure the forcing is already loaded
    # it's fine to run twice, and may help catching errors earlier
    update_forcing!(model)
    if haskey(config.input, "cyclic")
        update_cyclic!(model)
    end

    return model
end

"update the sbm_gwf model for a single timestep"
function update_sbm_gwf(model)
    @unpack lateral, vertical, network, clock, config = model

    inds_riv = network.index_river
    kinwave_it = get(config.model, "kin_wave_iteration", false)

    update_forcing!(model)
    if haskey(config.input, "cyclic")
        update_cyclic!(model)
    end

    # extract water levels h_av [m] from the land and river domains
    # this is used to limit open water evaporation
    vertical.waterlevel_land .= lateral.land.h_av .* 1000.0
    vertical.waterlevel_river[inds_riv] .= lateral.river.h_av .* 1000.0

    # vertical sbm concept is updated until snow state, after that (optional)
    # snow transport is possible
    update_until_snow(vertical, config)

    # lateral snow transport
    if get(config.model, "masswasting", false)
        snowflux_frac =
            min.(0.5, lateral.land.sl ./ 5.67) .* min.(1.0, vertical.snow ./ 10000.0)
        maxflux = snowflux_frac .* vertical.snow
        vertical.snow .= accucapacityflux(network.land, vertical.snow, maxflux)
        vertical.snowwater .= accucapacityflux(network.land, vertical.snowwater, vertical.snowwater .* snowflux_frac)
    end

    # update vertical sbm concept until recharge [mm]
    update_until_recharge(vertical, config)

    # set river stage (groundwater) to average h from kinematic wave
    lateral.subsurface.river.stage .= lateral.river.h_av .+ lateral.subsurface.river.bottom

    # determine stable time step for groundwater flow
    Δt_gw = stable_timestep(lateral.subsurface.flow.aquifer) # time step in day (Float64)
    Δt_sbm = (vertical.Δt / 86400.0) # vertical.Δt is in seconds (Float64)
    if Δt_gw < Δt_sbm
        @warn("stable time step Δt $Δt for groundwater flow is smaller than sbm Δt $Δt_sbm")
    end

    Q = zeros(vertical.n)
    # exchange of recharge between vertical sbm concept and groundwater flow domain
    lateral.subsurface.recharge.rate .= vertical.recharge ./ 1000.0
    # update groundwater domain
    update(lateral.subsurface.flow, Q, Δt_sbm)

    # determine excess head [m] (exfiltwater) in groundwater domain (head > surface) and reset head
    exfiltwater = lateral.subsurface.flow.aquifer.head .- min.(lateral.subsurface.flow.aquifer.head, lateral.subsurface.flow.aquifer.top)
    lateral.subsurface.flow.aquifer.head .= min.(lateral.subsurface.flow.aquifer.head, lateral.subsurface.flow.aquifer.top)

    # update vertical sbm concept (runoff, ustorelayerdepth and satwaterdepth)
    update_after_lateralflow(
        vertical,
        (vertical.altitude .- lateral.subsurface.flow.aquifer.head) .* 1000.0, # zi [mm] in vertical concept SBM
        exfiltwater .* 1000.0,
    )

    # update overland flow based on vertical runoff [mm] from vertical sbm concept
    lateral.land.qlat .=
        (vertical.runoff .* vertical.xl .* vertical.yl .* 0.001) ./ lateral.land.Δt ./
        lateral.land.dl
    update(
        lateral.land,
        network.land,
        frac_toriver = network.frac_toriver,
        river = lateral.river.rivercells,
        do_iter = kinwave_it,
    )

    # update river domain with net runoff from vertical sbm concept, overland flow
    # and groundwater flow to the river cells
    net_runoff_river =
        (vertical.net_runoff_river[inds_riv] .* vertical.xl[inds_riv] .*
        vertical.yl[inds_riv] .* 0.001) ./ vertical.Δt

    # flux from groundwater domain to river (Q to river from drains and groundwater)
    flux_gw = zeros(vertical.n)
    flux_gw[lateral.subsurface.river.index] = -lateral.subsurface.river.flux
    flux_gw[lateral.subsurface.drain.index] = flux_gw[lateral.subsurface.drain.index] - lateral.subsurface.drain.flux

    lateral.river.qlat .=
        (
            flux_gw[inds_riv] ./ lateral.river.Δt .+
            lateral.land.to_river[inds_riv] .+ net_runoff_river
        ) ./ lateral.river.dl
    update(lateral.river, network.river, do_iter = kinwave_it, doy = dayofyear(clock.time))

    write_output(model)

    # update the clock
    advance!(clock)

    return model
end