using WebIO, JSExpr, Observables
using Blink, Colors

assetpath(files...) = joinpath(@__DIR__, "..", "assets", files...)

struct Geometry
    scope::Scope
end

evaljs(scope, @js begin
    this.buffer_geometry = function (in_vertices, indices, in_normals)
        @var geometry = @new this.THREE.BufferGeometry();
        geometry.setIndex($(gindices));
        # itemSize = 3 because there are 3 values (components) per vertex
        geometry.addAttribute("position", @new this.THREE.BufferAttribute(vertices, 3))
        geometry.addAttribute("normal", @new this.THREE.BufferAttribute(normals, 3))
    end
end)

    vertices = reinterpret(Float32, decompose(Point3f0, geom))
    indices = reinterpret(UInt32, decompose(GLTriangle, geom))
function InstancedGeometry(geometry, attributes)
    @var circleGeometry = @new THREE.CircleBufferGeometry(1, 6);
    geometry = @new THREE.InstancedBufferGeometry();
    geometry.index = circleGeometry.index;
    geometry.attributes = circleGeometry.attributes;
    @var vertices = @new Float32Array($(in_vertices));
    @var normals = @new Float32Array($(in_normals));
    @var transbuff = @new Float32Array($translateArray[])
    instanced_trans = @new THREE.InstancedBufferAttribute(transbuff, 3, 1)
    geometry.addAttribute("translate", instanced_trans);

    @var scalebuff = @new Float32Array($scales[])
    instanced_scalebuff = @new THREE.InstancedBufferAttribute(scalebuff, 3, 1)
    geometry.addAttribute("scale", instanced_scalebuff);

end

function add_attribute!(geometry, name, attribute)
    evaljs(geometry.scope, @js begin
        @var buff = @new Float32Array($translateArray[])
        instanced_buff = @new THREE.InstancedBufferAttribute(buff, 3, 1)
        geometry.addAttribute($(name), instanced_buff);
    end)
    onjs(attribute, @js function (values)

    end)
end

function circles(image, w, h)

    threejs = Scope(imports = [
        "//cdnjs.cloudflare.com/ajax/libs/three.js/84/three.min.js",
        "//gitcdn.xyz/cdn/mattdesl/three-orbit-controls/v81.1.0/index.js"
    ]);
    image_obs = Observable(threejs, "image_obs", image)
    particleCount = 75000;
    translateArray = Observable(threejs, "translateArray", rand(particleCount * 3) .* 2.0 .- 1.0)

    scales = Observable(threejs, "scales", rand(particleCount * 3) .* 20.0 .- 1.0)

    camerapos = Observable(threejs, "camerapos", 1400.0)
    onjs(camerapos, @js (pos) -> (this.camera.position.z = pos))

    vert_shader = Observable(threejs, "vert_shader", read(assetpath("particle.vert"), String))
    frag_shader = Observable(threejs, "frag_shader", read(assetpath("particle.frag"), String))

    onimport(threejs, @js function (THREE, OrbitControlsModule)

        @var container = document.querySelector("#container");
        @var scene; @var renderer;
        @var geometry; @var material; @var mesh;
        @var globalscope = this;
        console.log(window.innerWidth + " " + window.innerHeight)
        function onWindowResize(event)
            globalscope.camera.aspect = window.innerWidth / window.innerHeight
            globalscope.camera.updateProjectionMatrix()
        end
        function animate()
            requestAnimationFrame(animate)
            render()
        end
        function render()
            @var time = performance.now() * 0.0005
            material.uniforms.time.value = time
            mesh.rotation.y = time * 0.4
            renderer.render(scene, globalscope.camera)
        end

        function init()
            renderer = @new THREE.WebGLRenderer()
            globalscope.camera = @new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 5000);
            globalscope.camera.position.z = 1400;
            scene = @new THREE.Scene();
            # geometry

            @var circleGeometry = @new THREE.CircleBufferGeometry(1, 6);
            geometry = @new THREE.InstancedBufferGeometry();
            geometry.index = circleGeometry.index;
            geometry.attributes = circleGeometry.attributes;

            @var transbuff = @new Float32Array($translateArray[])
            instanced_trans = @new THREE.InstancedBufferAttribute(transbuff, 3, 1)
            geometry.addAttribute("translate", instanced_trans);

            @var scalebuff = @new Float32Array($scales[])
            instanced_scalebuff = @new THREE.InstancedBufferAttribute(scalebuff, 3, 1)
            geometry.addAttribute("scale", instanced_scalebuff);

            # material
            @var img = $image_obs[];
            @var dummyRGBA = @new Uint8Array(img);
            tex = @new THREE.DataTexture(dummyRGBA, $w, $h,
                THREE.RGBAFormat,
            );
            tex.needsUpdate = true
            material = @new THREE.RawShaderMaterial(d(
                uniforms = d(
                    my_tex = d(value = tex),
                    time = d(value = 0.0)
                ),
                vertexShader = $vert_shader[],
                fragmentShader = $frag_shader[],
                depthTest = true,
                depthWrite = true
            ))
            mesh = @new THREE.Mesh(geometry, material)
            mesh.scale.set( 500, 500, 500 )
            scene.add(mesh)
            renderer.setPixelRatio(window.devicePixelRatio)
            renderer.setSize(window.innerWidth, window.innerHeight)
            container.appendChild(renderer.domElement)
            window.addEventListener("resize", onWindowResize, false)
        end
        init()
        animate()
    end)
    threejs(dom"div#container"())
end
using Blink, FixedPointNumbers
using FileIO

doge = load(joinpath(homedir(), ".julia/dev/Makie/src/glbackend/GLVisualize/assets/doge.png"))

img2 = vec(UInt8[
    getfield(doge[i, j], c).i
    for c in 1:4, i = 1:size(doge, 1), j = 1:size(doge, 2)
])
w = Window()
body!(w, circles(img2, size(doge, 1), size(doge, 2)))


"""
    GameNode

Node type for tree representing game play on a [`GamePool{N}`](@ref)

Fields are:

- `score`: a [`GameScore`](@ref) for this target based on the score that generated the node
- `children`: `Vector{GameNode}` of children of this node
"""
struct GameNode
    score::GuessScore
    children::Vector{GameNode}
end

AbstractTrees.children(gn::GameNode) = gn.children
AbstractTrees.nodetype(::GameNode) = GameNode

function AbstractTrees.printnode(io::IO, gn::GameNode)
    (; poolsz, guess, expected, entropy, score) = gn.score
    return join(
        IOContext(io, :compact => true), (score, guess, poolsz, entropy, expected), ", "
    )
end

"""
    tree(gp::GamePool, inds::AbstractVector{<:Integer})
    tree(gp::GamePool{N}, targets::AbstractVector{NTuple{N,Char}})
    tree(gp::GamePool, targets::AbstractVector{<:AbstractString})
    tree(gp::GamePool)

Return the root `GameNode` of a tree of the result of `playgame!.(Ref(gp), inds)`

A `print_tree` method is available for this tree.
"""
function tree(gp::GamePool{N,S}, targetinds::AbstractVector{<:Integer}) where {N,S}
    (; guesspool) = reset!(gp)
    nguesspool = length(guesspool)
    rootguess = first(gp.guesses)     # reset! preserves the first guess, which will be the root
    rootindex = rootguess.index
    guessmap = Dict{Int,GuessScore}(rootindex => rootguess)  # maps indexes to guesses
    pathmap = Dict{Int,Vector{Int}}()            # maps a guess index to a path
    activeinds = sizehint!(BitSet(), nguesspool) # keep track of indexes already encountered
    for t in targetinds
        if t ∉ activeinds                        # skip if this node has already been encountered
            (; guesses) = playgame!(gp, t)       # extract the guesses for the game
            inds = getfield.(guesses, :index)    # indexes for this game
            union!(activeinds, inds)             # add these to the activeinds
            for (j, k) in enumerate(inds)
                indviewj = view(inds, 1:j)
                @assert get!(pathmap, k, indviewj) == indviewj  # store or verify
                if j > 1
                    (; poolsz, index, guess, expected, entropy) = guesses[j]
                    (; score, sc) = guesses[j - 1]
                    g = (; poolsz, index, guess, expected, entropy, score, sc)
                    @assert get!(guessmap, k, g) == g           # store or verify
                end
            end
        end
    end
    childmap = Dict(i => BitSet() for i in activeinds)
    for path in getindex.(Ref(pathmap), activeinds)
        npath = length(path)
        npathm1 = npath - 1
        for i in 1:npathm1
            push!(childmap[path[i]], path[i + 1])
        end
    end
    allnodes = [GameNode(guessmap[k], GameNode[]) for k in activeinds]
    inversemap = Dict(k => i for (i, k) in enumerate(activeinds))
    for (i, k) in enumerate(activeinds)
        for j in childmap[k]
            push!(allnodes[i].children, allnodes[inversemap[j]])
        end
    end
    value = allnodes[inversemap[rootindex]]
    for node in PreOrderDFS(value)
        sort!(node.children; by=treesize, rev=true)
    end
    return value
end

function tree(gp::GamePool{N}, targets::AbstractVector{NTuple{N,Char}}) where {N}
    return tree(gp, [findfirst(==(t), gp.guesspool) for t in targets])
end

function tree(gp::GamePool{N}, targets::AbstractVector{<:AbstractString}) where {N}
    any(≠(N), length.(targets)) && throw(ArgumentError("all targets should have length $N"))
    return tree(gp, NTuple{N,Char}.(targets))
end

function tree(gp::GamePool, rng::AbstractRNG, n::Integer)
    vt = gp.validtargets
    return tree(gp, rand(rng, axes(vt, 1)[vt], n))
end

function tree(gp::GamePool)
    (; validtargets) = gp
    return tree(gp, view(axes(validtargets, 1), validtargets))
end

treesize(node) = 1 + mapreduce(treesize, +, children(node); init=0)


module BitmapGraphs

using LightGraphs, SparseArrays

export AbstractBMGraph, BMGraph, invert!, set_diag!, BitRow, SRow, nchunks, mk_msk, SBMGraph
import Base: setindex
#utils for iterating over bit-chunks
@inline _blsr(x) = x & (x-1)

struct LBitRow{chunk_T}
    len::Int
    chunks::chunk_T
end

#future feature
struct BitRow{chunk_T}
    chunks::chunk_T
end
struct SRow{N}
    chunks::NTuple{N, UInt64}
end
const _BitRow = Union{LBitRow, BitRow, SRow}# BitRow

#opt out of simd. Fixme once github.com/JuliaLang/julia/pull/31113 has landed.
Base.SimdLoop.simd_index(v::_BitRow, j::Int64, i) = j
Base.SimdLoop.simd_inner_length(v::_BitRow, j::Int64) = 1
Base.SimdLoop.simd_outer_range(v::_BitRow) = v

nchunks(br::_BitRow) = length(br.chunks)

Base.@propagate_inbounds function Base.in(br::_BitRow, idx)
    i1,i2 = Base.get_chunks_id(idx)
    @boundscheck ((0<i1<len(br)) || return false)
    @inbounds u = br.chunks[i1]
    return iszero(u & (1<< (i2 & 63)))
end

function Base.iterate(br::_BitRow)
    nchunks(br)==0 && return nothing
    return iterate(br, (1, @inbounds br.chunks[1]))
end

#@inline Base.length(br::BitRow) = sum(count_ones, br.chunks)
@inline Base.length(br::LBitRow) = br.len

@inline function Base.iterate(br::_BitRow, s)
    chunks = br.chunks
    i1, c = s
    while c==0
        i1 % UInt >= length(chunks) % UInt && return nothing
        i1 += 1
        @inbounds c = chunks[i1]
    end
    tz = trailing_zeros(c) + 1
    c = _blsr(c)
    return ((i1-1)<<6 + tz, (i1, c))
end

struct BMEdgeIter{chunkT, T}
    ne::Int
    chunks::chunkT
end
@inline Base.eltype(::BMEdgeIter{chunkT, T}) where {chunkT, T} = T
@inline Base.length(ei::BMEdgeIter) = ei.ne

@inline function Base.iterate(br::BMEdgeIter)
    length(br.chunks)>0 || return nothing
    return iterate(br, (1, 1, @inbounds br.chunks[1]))
end

@inline function Base.iterate(br::BMEdgeIter{chunkT, T}, s) where {chunkT, T}
    chunks = br.chunks
    nchunks = size(chunks,1)
    row, col, c = s
    while c==0
        if ((row % UInt) >= nchunks)
            col >= size(chunks, 2) && return nothing
            col += 1
            row,i2 = Base.get_chunks_id(col)
            @inbounds c = chunks[row, col] & (~UInt(0) << (i2 & 63) )
            continue
        end
        row += 1
        @inbounds c = chunks[row, col]
    end
    tz = trailing_zeros(c) + 1
    c = _blsr(c)
    return T((row-1)<<6 + tz, col) ,  (row, col, c)
end

#TODO utils for bitrows:
#intersection, union, xor, setminus, complement, count.
#need to come in broadcast and broadcast! variants.

abstract type AbstractBMGraph <:LightGraphs.AbstractGraph{Int} end

mutable struct BMGraph <: AbstractBMGraph
    adj_chunks::Matrix{UInt64}
    adj_mat::BitMatrix
    degrees::Vector{Int}
    ne::Int
end

function BMGraph(n::Int)
    nc = Base.num_bit_chunks(n)
    adj_mat = falses(nc*64, n)
    adj_chunks = reshape(adj_mat.chunks, nc, n)
    degs = zeros(Int, n)
    return BMGraph(adj_chunks, adj_mat, degs, 0)
end

function BMGraph(g::AbstractGraph)
    vertices(g) == Base.OneTo(nv(g))||throw(ArgumentError("BMGraphs only support `vertices isa Base.OneTo`"))
    res = BMGraph(nv(g))
    for ed in edges(g)
        add_edge!(res, src(ed), dst(ed))
    end
    return res
end
nchunks(g::BMGraph) = size(g.adj_chunks, 1)

function Base.copy(bm::BMGraph)
    adj_mat = copy(bm.adj_mat)
    adj_chunks = reshape(adj_mat.chunks, size(bm.adj_chunks))
    return BMGraph(adj_chunks, adj_mat, copy(bm.degs), bm.ne)
end

#interface:
LightGraphs.edgetype(::AbstractBMGraph) = LightGraphs.SimpleGraphs.SimpleEdge{Int}
Base.eltype(::AbstractBMGraph) = Int
LightGraphs.nv(g::AbstractBMGraph) = size(g.adj_chunks, 2)
LightGraphs.ne(g::BMGraph) = g.ne
LightGraphs.vertices(g::AbstractBMGraph) = Base.OneTo(nv(g))
LightGraphs.edges(g::AbstractBMGraph) = BMEdgeIter{typeof(g.adj_chunks), LightGraphs.SimpleGraphs.SimpleEdge{Int}}(ne(g), g.adj_chunks)
LightGraphs.is_directed(g::AbstractBMGraph) = false
LightGraphs.is_directed(::Type{<:AbstractBMGraph}) = false

LightGraphs.has_vertex(g::BMGraph, v) =  g in vertices(g)

Base.@propagate_inbounds LightGraphs.has_edge(g::BMGraph, e::AbstractEdge) = has_edge(g, src(e), dst(e))
Base.@propagate_inbounds LightGraphs.has_edge(g::BMGraph, e::Tuple{<:Integer, <:Integer}) = has_edge(g, e...)
Base.@propagate_inbounds LightGraphs.has_edge(g::BMGraph, s, d) = g.adj_mat[s, d]
Base.@propagate_inbounds function LightGraphs.outneighbors(g::BMGraph, v)
    vvv = view(g.adj_chunks, :, v)
    return LBitRow(g.degrees[v], vvv)
end
Base.@propagate_inbounds LightGraphs.inneighbors(g::AbstractBMGraph, v...) = outneighbors(g, v...)
Base.@propagate_inbounds LightGraphs.indegree(g::BMGraph, v::Integer) = g.degrees[v]
Base.@propagate_inbounds LightGraphs.outdegree(g::BMGraph, v) = g.degrees[v]
#Base.@propagate_inbounds LightGraphs.degree(g::BMGraph, v) = g.degrees[v] #causes method ambiguity errors

LightGraphs.add_edge!(g::BMGraph, e::AbstractEdge) = add_edge!(g, src(e), dst(e))
LightGraphs.add_edge!(g::BMGraph, e::Tuple{<:Integer, <:Integer}) = add_edge!(g, e...)
Base.@propagate_inbounds function LightGraphs.add_edge!(g::BMGraph, s, d)
    @boundscheck begin
        0 < d <= nv(g) || throw(BoundsError(g, d))
        0 < s <= nv(g) || throw(BoundsError(g, s))
    end
    @inbounds oldv = g.adj_mat[s,d]
    @inbounds g.adj_mat[s,d] = true
    @inbounds g.adj_mat[d,s] = true
    if !oldv
        g.ne += 1
        @inbounds g.degrees[s] += 1
        s != d && @inbounds g.degrees[d] += 1
    end
    return !oldv
end

Base.@propagate_inbounds function LightGraphs.rem_edge!(g::BMGraph, s, d)
    @boundscheck begin
        0 < d <= nv(g) || throw(BoundsError(g, d))
        0 < s <= nv(g) || throw(BoundsError(g, s))
    end
    @inbounds oldv = g.adj_mat[s,d]
    @inbounds g.adj_mat[s,d] = false
    @inbounds g.adj_mat[d,s] = false
    if oldv
        g.ne -= 1
        @inbounds g.degrees[s] -= 1
        s != d && @inbounds g.degrees[d] -= 1
    end
    return oldv
end

"""
invert!(bm::BMGraph,  no_selfloops = true)

Inverts the graph: All formerly present edges are now missing, and vice versa. This includes self-loops.
If `no_selfloops` is set, then all self-loops are removed afterwards.
"""
function invert!(bm::BMGraph, no_selfloops = true)
    nr, nc = size(bm)
    @inbounds for i=1:nc
        bm.degrees[i] = nc - bm.degrees[i]
    end
    bm.ne = sum(bm.degrees)
    @simd for i=1:length(bm.adj_chunks)
        @inbounds bm.adj_chunks[i] = ~bm.adj_chunks[i]
    end
    msk_e = Base._msk_end(nc)
    for j = 1:nr
        @inbounds bm.adj_chunks[j, nr] &= msk_e
    end
    if no_selfloops
        set_diag!(bm, false)
    end

    return bm
end

"""
set_diag!(bm::BMGraph, b::Bool)

Sets the diagonal to b, i.e. adds or removes all self-loops.
"""
function set_diag!(bm::BMGraph, b::Bool)
    if b
        for i=1:nv(bm)
            add_edge!(bm, i, i)
        end
    else
        for i=1:nv(bm)
            rem_edge!(bm, i, i)
        end
    end
    bm
end

"""
induced_subgraph(BMGraph, g::Lightgraphs.AbstractGraph, vertices)

Returns the induced subgraph as a BMGraph. Assumes that input vertices and `neighbors` are sorted.
"""
function LightGraphs.induced_subgraph(::Type{BMGraph}, g::LightGraphs.AbstractGraph, vertices)
    n = length(vertices)
    bm = BMGraph(n)
    for (i,v) in enumerate(vertices)
        vi = 1
        #vv = vertices[1]
        @inbounds for u in neighbors(g, v)
            u <  vertices[vi] && continue
            while vertices[vi] < u
                vi < n || @goto done
                vi += 1
            end
            u < vertices[vi] && continue
            add_edge!(bm, i, vi)
            vi += 1
        end
        @label done
    end
    return bm
end

_true(args...) = true
_false(args...) = false

"""
induced(::Type{BMGraph}, mat::SparseMatrixCSC, vertices, edgepred = _true, bailout = _false)

Returns the induced subgraph, of the graph with adjacency matrix `mat` for which `edgepred(i, j, mat[i,j])` evaluates to `true`.
The matrix is assumed symmetric and `vertices` is assumed sorted.

The predicate `bailout(bm, i)` is called after each constructed row `i`. If it returns `true`, then the partial result is returned.
"""
function LightGraphs.induced_subgraph(::Type{BMGraph}, mat::SparseMatrixCSC, vertices, edgepred = _true, bailout = _false)
    n = length(vertices)
    bm = BMGraph(n)
    n > 1 || return bm
    for (i,v) in enumerate(vertices)
        vi = 1
        ran =  mat.colptr[v]:mat.colptr[v+1]-1
        for ui in ran #neighbors(g, v)
            @inbounds u = mat.rowval[ui]
            @inbounds u <  vertices[vi] && continue

            while @inbounds vertices[vi] < u
                vi < n || @goto done
                vi += 1
            end
            @inbounds u <  vertices[vi] && continue
            edgepred(v, u, @inbounds mat.nzval[ui]) && (@inbounds bm.adj_mat[vi, i] = true)
            vi += 1
            vi < n || @goto done
        end
        @label done
        deg = sum(count_ones, view(bm.adj_chunks, :, i))
        bm.degrees[i] = deg
        bm.ne += deg
        bailout(bm, i) && return bm
    end
    return bm
end

#example algorithm
function LightGraphs.gdistances(g::BMGraph, s)
    res = fill(-1, nv(g))
    nchunks = size(g.adj_chunks, 1)
    visited = zeros(UInt, nchunks)
    todo = zeros(UInt, nchunks)
    nxt = zeros(UInt, nchunks)
    done = false
    dist = 1
    todo .= outneighbors(g, s).chunks
    visited .= outneighbors(g, s).chunks
    while !done
        done = true
        for i in BitRow(todo)
            @simd for j = 1:nchunks
                @inbounds nxt[j] |= g.adj_chunks[j, i] & ~visited[j]
            end
            res[i] = dist
            done = false
        end
        done && break
        visited .|= nxt
        todo .= nxt
        fill!(nxt, 0)
        dist += 1
    end
    res[s] = 0
    res
end

###############
#Static graphs

#Base.in, Base.iterate, are defined above
@inline Base.length(a::SRow) = count_ones(a)
Base.eltype(::SRow) = Int

(Base.:&)(a::SRow{N}, b::SRow{N}) where N = SRow(ntuple(i->(@inbounds a.chunks[i] & b.chunks[i]), N))
(Base.:|)(a::SRow{N}, b::SRow{N}) where N = SRow(ntuple(i->(@inbounds a.chunks[i] | b.chunks[i]), N))
(Base.xor)(a::SRow{N}, b::SRow{N}) where N = SRow(ntuple(i->(@inbounds xor(a.chunks[i], b.chunks[i])), N))
(Base.:~)(a::SRow{N}) where N = SRow(ntuple(i->(@inbounds ~a.chunks[i]), N))
Base.zero(::Type{SRow{N}}) where N = SRow(ntuple(i->UInt64(0), N))
Base.iszero(a::SRow{N}) where N = all(iszero, a.chunks)
Base.isempty(a::SRow{N}) where N = all(iszero, a.chunks)

Base.@propagate_inbounds function Base.setindex!(a::Base.RefValue{SRow{N}}, b, idx) where N
    i1,i2 = Base.get_chunks_id(idx)
    @boundscheck ((0<i1<=N) || throw(BoundsError(a, idx)))
    @inbounds u = a[].chunks[i1]
    nu = ifelse(b, u | (1<< (i2 & 63)), u & ~(1<< (i2 & 63)) )
    ptr = convert(Ptr{UInt64}, pointer_from_objref(a))
    GC.@preserve a unsafe_store!(ptr, nu, i1)
    b
end

Base.@propagate_inbounds function mk_msk(::Type{SRow{N}}, idx) where N
    i1,i2 = Base.get_chunks_id(idx)
    @boundscheck ((0<i1<=N) || throw(BoundsError(SRow{N}, idx)))
    r = Ref(zero(SRow{N}))
    ptr = convert(Ptr{UInt64}, pointer_from_objref(r))
    GC.@preserve r begin
    for i=1:i1-1
        unsafe_store!(ptr, -1%UInt, i)
    end
    unsafe_store!(ptr, Base._msk_end(idx), i1)
    end
    return r[]
end

#=
function pop_(a::SRow{N}) where N
    i = 1
    @inbounds while i <= N && iszero(a.chunks[i])
        i += 1
    end
    i == N+1 && return -1, a
    @inbounds idx = trailing_zeros(a.chunks[i]) + 1 + (i-1)<<6
    r = Ref(a)
    ptr = convert(Ptr{UInt64}, pointer_from_objref(a))
    unsafe_store!(ptr, _blsr(a.chunks[i]), i)
    return idx, a[]
end
=#

Base.@propagate_inbounds Base.setindex(a::SRow{N}, b, idx) where N = begin r=Ref(a); setindex!(r, b, idx); r[] end

@inline function Base.count_ones(a::SRow{N}) where N
    res = 0
    for i=1:N
        @inbounds res+= count_ones(a.chunks[i])
    end
    res
end
Base.@propagate_inbounds function Base.count_ones(a::SRow{N}, k) where N
    res = 0
    i1, i2 = Base.get_chunks_id(k)
    for i = 1:i1-1
        res += count_ones(a.chunks[i])
    end
    res += count_ones(a.chunks[i1] & Base.msk_end(i1))
    res
end

##SBMGraph
struct SBMGraph{N} <:AbstractBMGraph
    adj_chunks::Matrix{UInt64}
end
@inline SBMGraph(g::BMGraph) = SBMGraph(g, Val(nchunks(g)))
SBMGraph(g::BMGraph, ::Val{N}) where N = SBMGraph{N}(g.adj_chunks)

nchunks(g::SBMGraph{N}) where N = N
Base.@propagate_inbounds function Base.getindex(g::SBMGraph{N}, idx) where N
    @boundscheck checkbounds(g.adj_chunks, 1, idx)
    off = N*(idx-1)
    return SRow(ntuple(i -> (@inbounds g.adj_chunks[i + off]), N))
end
Base.@propagate_inbounds LightGraphs.outneighbors(g::SBMGraph, idx) = g[idx]
function LightGraphs.ne(g::SBMGraph)
    s = sum(count_ones, g.adj_chunks)
    for i=1:nv(g)
        i in g[i] && (s+=1)
    end
    return s>>1
end
Base.@propagate_inbounds LightGraphs.has_edge(g::SBMGraph, s, d) = d in g[s]
Base.@propagate_inbounds LightGraphs.indegree(g::SBMGraph, v::Integer) = count_ones(g[v])
Base.@propagate_inbounds LightGraphs.outdegree(g::SBMGraph, v::Integer) = count_ones(g[v])

LightGraphs.gdistances(g::SBMGraph{N}, s) where N = gdistances!(g, s, fill(0, nv(g)))
function LightGraphs.gdistances!(g::SBMGraph{N}, s, res) where N
    resize!(res, nv(g))
    fill!(res, 0)
    T = SRow{N}
    @inbounds todo = visited = g[s]
    nxt = zero(T)
    dist = 1
    done = false
    while !done
        done = true
        for i in todo
            @inbounds nxt |= g[i] & ~visited
            @inbounds res[i] = dist
            done = false
        end
        done && break
        visited |= nxt
        todo = nxt
        nxt = zero(T)
        dist += 1
    end
    res[s] = 0
    res
end


using Arrow

function save_model(;
    output_dir,
    fig,
    recorded_measures,
    model,
    inputs,
    feature_names,
    n_jets,
    classes,
    outputs,
    weights,
    total_weights,
    idx_train, idx_test, idx_validation,
)
    isdir(output_dir) || mkdir(output_dir)

    if fig !== nothing
        fig.savefig(joinpath(output_dir, "losses.pdf"))
    end

    Arrow.write(
        joinpath(output_dir, "recorded_measures.arrow"),
        recorded_measures,
    )

    let ps = Flux.params(cpu(model)) |> collect
        df = DataFrame()
        if n_jets > 0
            cola = popfirst!(ps)
            @assert cola isa CoLa
            lola = popfirst!(ps)
            @assert lola isa LoLa
            df = DataFrame(
                :cola => [cola.C],
                :lola_w_E => [lola.w_E],
                (Symbol(:lola_w_d_, i) => [lola.w_ds[i]] for i in eachindex(lola.w_ds))...,
            )
        end
        df = hcat(
            df,
            DataFrame(
                (Symbol(i % Bool ? :W_ : :b_, fld1(i, 2)) => [p] for (i, p) in enumerate(ps))...,
            )
        )
        Arrow.write(
            joinpath(output_dir, "layer_params.arrow"),
            df,
        )
    end

    all_features = DataFrame();
    if n_jets > 0
        lola_out = model[1](gpu(inputs))[1:length(Vars.names_lola_out), :] |> cpu
    end
    for (label, idx) in [
        :train => idx_train,
        :test => idx_test,
        :validation => idx_validation,
    ]
        idx = cpu(idx)
        input_features = Symbol.(feature_names) .=> eachrow(view(inputs, :, idx))
        output_expected = :output_expected => outputs[idx]
        ŷ = softmax(model(gpu(inputs[:, idx]))) |> cpu
        output_predicted = [Symbol(:output_predicted_, l) => ŷ[i, :] for (i, l) in enumerate(classes)]
        _weights = [:weights => weights[idx], :weights_norm => total_weights[idx]]
        _lola_out = if n_jets > 0
            Symbol.(Vars.names_lola_out) .=> eachrow(view(lola_out, :, idx))
        else
            ()
        end
        kind = :kind => fill(label, length(idx))
        append!(all_features, DataFrame(
            input_features...,
            output_expected,
            output_predicted...,
            _weights...,
            _lola_out...,
            kind,
        ))
    end

    Arrow.write(
        joinpath(output_dir, "all_features.arrow"),
        all_features,
    )
end


module Visualizing
  # using Graphs
  # using NetworkLayout
  # using GLMakie
  # using GraphMakie

  using ..Sources
  using ..DependencyTree
  using ..Converting

  function visualize(source::AbstractSource, file_title::String)
    sentences = Converting.convert(source) |> (text -> split(text, r"SENTENCE_END")) .|> strip

    for (index, sentence) in enumerate(sentences)
      filename = "$(file_title)_$(index).pdf"
      draw_sentence_graph(sentence, filename)
    end
  end

  function draw_sentence_graph(sentence, filename::String)
    g = Graphs.SimpleDiGraph()
    add_vertex!(g)
    nodelabel = ["ROOT-0"]
    edgelabel = Vector{String}()
    lines = split(sentence, "\n")

    foreach(lines) do line
      _, _, token = split(line)
      add_vertex!(g)
      push!(nodelabel, token)
    end

    for (index, line) in enumerate(lines)
      dependency_label, head_token, = split(line)
      head_index = findfirst(token -> head_token == token, nodelabel)

      push!(edgelabel, dependency_label)
      add_edge!(g, head_index , index + 1)
    end

    GraphMakie.graphplot(
      g,
      nlabels=nodelabel,
      nlabels_distance = 15,
      elabels = edgelabel,
      layout=NetworkLayout.Buchheim(),
      elabels_rotation=0,
      elabels_distance=25
    )
  end
end


using Test
using CUDA
using Random
using Rectangle

include("../environments/dummy_app.jl")

function kernel_test_is_point_in_rect(
    point_x,
    point_y,
    rect_x,
    rect_y,
    width,
    height,
    result,
)

    threadID = threadIdx().x

    result[threadID] = is_point_in_rect(point_x, point_y, rect_x, rect_y, width, height)

    return
end

@testset "Point in Rect" begin

    app_width = 600
    app_height = 400

    result_gpu = CUDA.fill(true, 1)

    for i = 1:10000

        point_x = rand(1:app_width)
        point_y = rand(1:app_width)

        rect_x = rand(1:app_width)
        rect_y = rand(1:app_width)
        rect_width = rand(100:400)
        rect_height = rand(50:200)

        CUDA.@cuda threads = 1 blocks = 1 kernel_test_is_point_in_rect(
            point_x,
            point_y,
            rect_x,
            rect_y,
            rect_width,
            rect_height,
            result_gpu,
        )
        CUDA.synchronize()

        result = Array(result_gpu)

        rect = Rect(rect_x, rect_y, rect_x + rect_width, rect_y + rect_height)
        @test inside((point_x, point_y), rect) == result[1]

    end

end


using Lifting
using Test

@test nothing === makeDays()
@testset "Struct" begin
    @test isdefined(Lifting, :makeDays)
    SampleExercise = Exercise(;
        name = "Swiss Bar Bench",
        modality = ["Block", "Semi-Supinated"],
        equipment = "Swiss Bar",
        size = "1 inch",
        muscles = ["Pecs", "Triceps", "Front Delts"],
        trainingMax = 100,
        roundBase = 2.5,
        roundMode = floor,
    )
    @test SampleExercise == SampleExercise[1]
    @test length(SampleExercise) == 1
    for i in SampleExercise
        @test SampleExercise == i
    end

    SampleExercise2 =
        Exercise(; name = "Squat", trainingMax = 130, roundBase = 2.5, roundMode = floor)
    @test SampleExercise.name == "Swiss Bar Bench"
    @test SampleExercise.modality == ["Block", "Semi-Supinated"]
    @test SampleExercise.equipment == "Swiss Bar"
    @test SampleExercise.size == "1 inch"
    @test SampleExercise.muscles == ["Pecs", "Triceps", "Front Delts"]
    @test SampleExercise.trainingMax == 100
    @test SampleExercise.roundBase == 2.5
    @test SampleExercise.roundMode == floor

    SampleScheme = SetScheme(;
        type = ["Long Rest", "Longer Rest", "Longest Rest"],
        sets = [1, 2, 1],
        reps = [12, 14, 10],
        intensity = [7, 8.5, 9.5],
        roundMode = [floor, floor, ceil],
        rpeMode = true,
    )
    SampleScheme2 = SetScheme(;
        type = ["Long Rest", "Longer Rest", "Longest Rest"],
        sets = [1, 2, 1],
        reps = [12, 14, 10],
        intensity = [7, 8.5, 9.5],
        roundMode = [floor, floor, ceil],
        rpeMode = true,
    )
    @test SampleScheme.type == ["Long Rest", "Longer Rest", "Longest Rest"]
    @test SampleScheme.sets == [1, 2, 1]
    @test SampleScheme.reps == [12, 14, 10]
    @test SampleScheme.intensity ==
          [0.6046188561906853, 0.6000914761292184, 0.6936559685445272]
    @test SampleScheme.roundMode == [floor, floor, ceil]
    @test SampleScheme.rpeMode == true
    @test SampleScheme.addWeight == [0, 0, 0]
    @test SampleScheme.wght == [0, 0, 0]

    SampleScheme = SetScheme(;
        type = ["Long Rest", "Longer Rest", "Longest Rest"],
        sets = [1, 2, 1],
        reps = [12, 14, 10],
        intensity = [0.7, 0.65, 0.75],
        addWeight = [2.5, 5, 0],
    )
    @test SampleScheme.intensity == [0.7, 0.65, 0.75]
    @test SampleScheme.roundMode == [floor, floor, floor]
    @test SampleScheme.rpeMode == false
    @test SampleScheme.wght == [0, 0, 0]
    for i in SampleScheme
        @test SampleScheme == i
    end

    SampleProgression = Progression(;
        type = LinearProgression(),
        name = "Progression Name",
        setScheme = SampleScheme,
    )
    @test SampleProgression == SampleProgression[1]
    @test length(SampleProgression) == 1
    for i in SampleProgression
        @test SampleProgression == i
    end

    SampleProgression2 = Progression(;
        type = LinearProgression(),
        name = "Progression Name",
        sessions = 1,
        period = 2,
        setScheme = [deepcopy(SampleScheme), SampleScheme2],
    )

    @test SampleProgression.type == LinearProgression()
    @test SampleProgression.name == "Progression Name"
    @test SampleProgression.setScheme == SampleScheme

    calcWeights(SampleExercise, SampleProgression)
    calcWeights(SampleExercise2, SampleProgression2)
    @test SampleProgression.setScheme.wght == 100 * [0.7, 0.65, 0.75] + [2.5, 5, 0]

    struct SampleProgramme <: AbstractProgramme end
    exerProg = Dict()
    push!(
        exerProg,
        "SampleExercise" => (exercise = SampleExercise, progression = SampleProgression),
    )
    push!(
        exerProg,
        "SampleExercise2" => (exercise = SampleExercise2, progression = SampleProgression2),
    )

    function makeDays(::SampleProgramme, exerProg)
        week = [[], [], [], [], [], [], []]
        SampleExercise = exerProg["SampleExercise"].exercise
        SampleProgression = exerProg["SampleExercise"].progression
        push!(week[1], SampleExercise, SampleProgression)   # Day 1
        push!(week[1], SampleExercise, SampleProgression)   # Day 1
        insert!(week[1], 1, SampleExercise2, SampleProgression2)   # Day 1
        push!(week[2], "Rest")  # Day 2
        push!(week[3], SampleExercise, SampleProgression)   # Day 3
        push!(week[4], "Rest")  # Day 4
        push!(week[5], SampleExercise, SampleProgression)   # Day 5
        push!(week[5], "Rest")  # Day 6
        push!(week[7], "Rest")  # Day 7
        return week
    end

    week = makeDays(SampleProgramme(), exerProg)
    sampleProgramme = Programme(SampleProgramme(), "SampleProgramme", exerProg, week)
    @test sampleProgramme.type == SampleProgramme()
    @test sampleProgramme.name == "SampleProgramme"
    @test sampleProgramme.exerProg == exerProg
    @test sampleProgramme.days == week
    test = []
    push!(test, SampleExercise2, SampleProgression2)
    @test week[1][1] == test[1]
    @test sampleProgramme[1] == sampleProgramme.days[1]
    @test sampleProgramme[1:2:3] == [sampleProgramme.days[1], sampleProgramme.days[3]]
end


using DataDeps

register(DataDep(
  "cylinder-bands",
  "http://archive.ics.uci.edu/ml/datasets/Cylinder+Bands",
  "http://archive.ics.uci.edu/ml/machine-learning-databases/cylinder-bands/bands.data",
  "31b4bd7f6cbca567e06199ae8bbe085c00d64657fcb2daad0c214cb4d08076f7",
  post_fetch_method=(path -> begin
    # There is a misplaced line break on line 522, we should get rid of it
    lines = readlines(path)
    lines[522] *= lines[523]
    deleteat!(lines, 523)
    write(path, join(lines, '\n'))

    UCIData.process_dataset(path,
        target_index=40,
        feature_indices=1:39,
        categoric_indices=2:20,
        footerskip=8,
    )
  end),
))


using DataFrames
using NetCDF
using IcanProj
using Statistics
using CSV
using ProgressMeter

function write_netcdfs(path, savefolder, res, frac_landuse, lai_landuse, hcan_landuse)

    filename = joinpath(path, savefolder, "params_$(res).nc")

    isfile(filename) && rm(filename, force = true)

    dim_space = collect(1:size(frac_landuse, 1))
    dim_classes = collect(1:size(frac_landuse, 2))

    nccreate(filename, "frac_landuse", "dim_space", dim_space, "dim_classes", dim_classes)
    nccreate(filename, "lai_landuse", "dim_space", dim_space, "dim_classes", dim_classes)
    nccreate(filename, "hcan_landuse", "dim_space", dim_space, "dim_classes", dim_classes)

    ncwrite(frac_landuse, filename, "frac_landuse")
    ncwrite(lai_landuse, filename, "lai_landuse")
    ncwrite(hcan_landuse, filename, "hcan_landuse")

    ncclose(filename)

end

function aggregate_params(path, res, name)

    @info res

    # Metadata from table

    df_landuse = CSV.File(joinpath(dirname(pathof(IcanProj)), "..", "data", "landuse.csv"), rows_for_type_detect = typemax(Int)) |> DataFrame

    # Metadata from input netcdfs

    file_in = joinpath(path, "forcings_st", "tair_$(res).nc")

    # Grid cell indicies in netcdf files

    ind_nc = Int.(ncread(file_in, "id"))

    # Leaf area index and canopy height

    lai = [1.4, 4.3, 6.7, 9.1, 0.9, 2.4, 2.3, 4.4, 0.1, 0.2, 0.3, 0.4, 0]
    hcan = [7.5, 12.3, 16.8, 22, 7.5, 11.6, 17, 17.2, 4.9, 8.4, 12.2, 18.3, 0]

    # Output arrays for forest runs

    landuse_classes = 3

    frac_landuse = Array{Float64}(undef, length(ind_nc), landuse_classes)
    lai_landuse = Array{Float64}(undef, length(ind_nc), landuse_classes)
    hcan_landuse = Array{Float64}(undef, length(ind_nc), landuse_classes)

    @showprogress 1 "Running for ..." for i in eachindex(ind_nc)

        ind_cell = ind_nc[i]

        irows = findall(df_landuse[Symbol(name)] .== ind_cell)

        # No data available - define as open areas

        if isempty(irows)

            frac_landuse[i, 1] .= 1

            frac_landuse[i, 2:end] .= 0

            lai_landuse[i, :] .= 0

            hcan_landuse[i, :] .= 0

            continue

        end

        # Compute total fraction

        tmp = colwise(mean, df_landuse[irows, 9:end])

        isorted = sortperm(tmp, rev=true)

        ifinal = isorted[1:landuse_classes]

        frac_total = sum(tmp[ifinal])

        # Total fraction is too small - define as open areas

        if frac_total < 0.1

            frac_landuse[i, 1] .= 1

            frac_landuse[i, 2:end] .= 0

            lai_landuse[i, :] .= 0

            hcan_landuse[i, :] .= 0

            continue

        end

        # Data is valid - use landuse classes

        frac_landuse[i, :] = tmp[ifinal] ./ frac_total

        lai_landuse[i, :] = lai[ifinal]

        hcan_landuse[i, :] = hcan[ifinal]

    end

    # Save to netcdfs for forest runs

    write_netcdfs(path, "params_forest", res, frac_landuse, lai_landuse, hcan_landuse)

    # Deal with simulations omitting forest processes

    frac_landuse = ones(length(ind_nc), 1)
    lai_landuse = zeros(length(ind_nc), 1)
    hcan_landuse = zeros(length(ind_nc), 1)

    # Save to netcdfs for forest runs

    write_netcdfs(path, "params_open", res, frac_landuse, lai_landuse, hcan_landuse)

end

# Settings

path = "/data04/jmg/fsm_simulations/netcdf"

res_name= Dict("1km" => "ind_senorge",
               "5km" => "ind_5km",
               "10km" => "ind_10km",
               "25km" => "ind_25km",
               "50km" => "ind_50km")

for (res, name) in res_name

    aggregate_params(path, res, name)

end


using Video4Linux
using ImageView
using Colors, ColorTypes

# Warning! no memory and device protection is implemented yet, therefore doing things out of order will cause julia to crash!

## NOTE: if your device does not support read try using Video4Linux.IO_METHOD_MMAP
set_io_method(Video4Linux.IO_METHOD_READ)

## open device
fid = open_device("/dev/video0")

## init_device(fd, force_format);
init_device(fid)

## start_capturing(fd);
start_capturing(fid)

## mainloop(fd, frame_count);
mainloop( fid, 1 )

## copy_buffer_bytes, copy the image buffer bytes to uint8 vector, the lenght will depend on the pixel format
imbuff = copy_buffer_bytes(640*480*2)
## copy frame data: raw + width + height + pixelformat
frame = Video4Linux.copy_buffer_frame()
#do some convertions on frame
img = Video4Linux.convert(Gray, frame)
imc = Video4Linux.convert(YCbCr, frame)
imshow(img)

## stop_capturing(fd);
stop_capturing(fid)

## uninit_device();
uninit_device(fid)

## close device
close_device(fid)

## kinect color image; manually set the driver to 640x480 YCbCr 4:2:2 for this test (can be done with qv4l2)

imYCbCr = convertUYVYtoYCbCr(imbuff, 640, 480)
#convert to rgb and display
imshow(convert.(RGB,imYCbCr))

## kinect 1 depth image, set kernel to depth with:
    #sudo rmmod gspca_kinect
    #sudo modprobe gspca_kinect depth_mode = 1

depthvec = convertY10BtoU16(imbuff[1:384000])

depthim = reshape(depthvec,(640,480))'
imshow(depthim)


include("../imports.jl")

@testset ExtendedTestSet "`Project`" begin
    tfm = Project(Translation(2, 2))
    bounds = Bounds((50, 50))
    @test projectionbounds(tfm, getprojection(tfm, bounds), bounds) == Bounds((3:52, 3:52))

    image = Image(rand(50, 50))
    @test_nowarn apply(tfm, image)
    timage = apply(tfm, image)
    @test timage.data[1] == image.data[1]
end


function importcscmat(
    m::Integer,
    n::Integer,
    colptr,
    rowindices,
    values::Vector{T};
    jumbled::Bool = false,
    desc::Descriptor = Descriptors.NULL
) where {T}
A = Ref{libgb.GrB_Matrix}()
m = libgb.GrB_Index(m)
n = libgb.GrB_Index(n)
colsize = libgb.GrB_Index(sizeof(colptr))
rowsize = libgb.GrB_Index(sizeof(rowindices))
valsize = libgb.GrB_Index(sizeof(values))

col = Ptr{libgb.GrB_Index}(Libc.malloc(colsize))
unsafe_copyto!(col, Ptr{UInt64}(pointer(colptr .- 1)), length(colptr))
row = Ptr{libgb.GrB_Index}(Libc.malloc(rowsize))
unsafe_copyto!(row, Ptr{UInt64}(pointer(rowindices .- 1)), length(rowindices))
val = Ptr{T}(Libc.malloc(valsize))
unsafe_copyto!(val, pointer(values), length(values))
libgb.GxB_Matrix_import_CSC(
    A,
    toGrB_Type(T),
    m,
    n,
    Ref{Ptr{libgb.GrB_Index}}(col),
    Ref{Ptr{libgb.GrB_Index}}(row),
    Ref{Ptr{Cvoid}}(val),
    colsize,
    rowsize,
    valsize,
    false,
    jumbled,
    desc
)
return GBMatrix{T}(A[])
end

function toGB(S::SparseMatrixCSC)
    return importcscmat(S.m, S.n, S.colptr, S.rowval, S.nzval)
end

function importcscvec(
    n::Integer, vi, vx::Vector{T};
    jumbled::Bool = false, desc::Descriptor = Descriptors.NULL
) where {T}
    v = Ref{libgb.GrB_Vector}
    n = libgb.GrB_Index(n)
    vi_size = libgb.GrB_Index(sizeof(vi))
    vx_size = libgb.GrB_Index(sizeof(vx))

    indices = Ptr{libgb.GrB_Index}(Libc.malloc(vi_size))
    unsafe_copyto!(indices, Ptr{UInt64}(pointer(vi .- 1)), length(vi))

    values = Ptr{libgb.GrB_Index}(Libc.malloc(vx_size))
    unsafe_copyto!(values, pointer(vx), length(vx))
    libgb.GxB_Vector_import_CSC(
        v,
        toGrB_Type(T),
        n,
        Ref{Ptr{libgb.GrB_Index}}(indices),
        Ref{Ptr{Cvoid}}(values),
        vi_size,
        vx_size,
        false,
        length(vx),
        false,
        desc
    )
end

function toGB(v::SparseVector)
    return importcscvec(v.n, v.nzind, v.nzval)
end

function importdensematrix(
    m::Integer, n::Integer, A::Matrix{T};
    desc::Descriptor = Descriptors.NULL
) where {T}
    C = Ref{libgb.GrB_Matrix}()
    m = libgb.GrB_Index(m)
    n = libgb.GrB_Index(n)
    Asize = libgb.GrB_Index(sizeof(A))

    Ax = Ptr{T}(Libc.malloc(Asize))
    unsafe_copyto!(Ax, pointer(A), length(A))
    libgb.GxB_Matrix_import_FullC(
        C,
        toGrB_Type(T),
        m,
        n,
        Ref{Ptr{Cvoid}}(Ax),
        Asize,
        false,
        desc
    )
    return GBMatrix{T}(C[])
end

function toGB(M::Matrix)
    return importdensematrix(size(M, 1), size(M, 2), M)
end

function importdensevec(
    n::Integer, v::Vector{T};
    desc::Descriptor = Descriptors.NULL
) where {T}
    w = Ref{libgb.GrB_Vector}()
    n = libgb.GrB_Index(n)
    vsize = libgb.GrB_Index(sizeof(v))

    vx = Ptr{T}(Libc.malloc(vsize))
    unsafe_copyto!(vx, pointer(v), length(v))
    libgb.GxB_Vector_import_Full(
        w,
        toGrB_Type(T),
        n,
        Ref{Ptr{Cvoid}}(vx),
        vsize,
        false,
        desc
    )
    return GBVector{T}(w[])
end

function toGB(v::Vector)
    return importdensevec(size(v)..., v)
end


#number of subscribers when the subreddut is identified by its reddit id
#example: the "resumes" subreddit has id=t5_2r0bv
function idsubscribers(id::AbstractString)
	resp=Requests.get("https://www.reddit.com/api/info/.json?id="*id)
	resp=Requests.json(resp)
	resp["data"]["children"][1]["data"]["subscribers"]
end

#number of subscribers when the subreddut is identified by its name
function subscribers(sub::AbstractString)
	resp=Requests.get("https://www.reddit.com/r/"*sub*"/about.json")
	resp=Requests.json(resp)
	resp["data"]["subscribers"]
end

#convert datetime tuple to Unix time used in reddit timestamps
#timestamp intervals in cloudsearch searches is of the form 1420113600..1420200000
reddittime(yyyy,mm,dd,hh,min,sec)=Int(Dates.datetime2unix(DateTime(yyyy,mm,dd,hh,min,sec)))

#count the number of posts (top coded at 100) between two dates on a given subreddit
function countposts(subreddit::AbstractString,firstday::Tuple{Int64,Int64,Int64},lastday::Tuple{Int64,Int64,Int64})
	timestamp=string(reddittime(firstday[1],firstday[2],firstday[3],00,00,00))*".."*string(reddittime(lastday[1],lastday[2],lastday[3],00,00,00))
	url="https://www.reddit.com/r/"*subreddit*"/search.api?rank=title&limit=100&q=timestamp%3A"*timestamp*"&restrict_sr=on&syntax=cloudsearch"
	# println(url)
	resp=Requests.json(get("https://www.reddit.com/r/"*subreddit*"/search.api?rank=title&limit=200&q=timestamp%3A"*timestamp*"&restrict_sr=on&syntax=cloudsearch"));
	length(resp["data"]["children"])
end

#count the number of daily posts (top-coded at 100) for ndays days
function countdailyposts(subreddit::AbstractString,firstday::Date,ndays::Int64=3)
	dates=collect(firstday:firstday+Dates.Day(ndays-1))
	ts=TimeSeries.TimeArray(dates,zeros(length(dates)),["ts"])
	for i=1:ndays
		ts.values[i]=countposts(subreddit,Dates.yearmonthday(dates[i]),Dates.yearmonthday(dates[i]+Dates.Day(1)))
		sleep(.1)
	end
	ts
end

#parse the /about/traffic page of a subreddit
#2nd column is uniques and first columns is pageviews
function traffic(sub::AbstractString)
	resp=Requests.get("https://www.reddit.com/r/"*sub*"/about/traffic.json")
	resp=Requests.json(resp)
	n=length(resp["hour"])
	hourly=Array(Any,n,3)
	for i=1:n
		hourly[i,1]=Dates.unix2datetime(resp["hour"][i][1])
		hourly[i,2]=resp["hour"][i][2]
		hourly[i,3]=resp["hour"][i][3]
	end
	n=length(resp["day"])
	daily=Array(Any,n,3)
	for i=1:n
		daily[i,1]=Dates.Date(Dates.unix2datetime(resp["day"][i][1]))
		daily[i,2]=resp["day"][i][2]
		daily[i,3]=resp["day"][i][3]
	end
	n=length(resp["month"])
	monthly=Array(Any,n,3)
	for i=1:n
		dt=Dates.unix2datetime(resp["month"][i][1])
		monthly[i,1]=string(year(dt))*"-"*string(month(dt))
		monthly[i,2]=resp["month"][i][2]
		monthly[i,3]=resp["month"][i][3]
	end
	hourly,daily,monthly
end


module VertexSafeGraphs

import LightGraphs
const LG = LightGraphs

export VSafeGraph

"""
    VSafeGraph{T, G<:LG.AbstractGraph{T}, V<:AbstractVector{Int}}

A `LightGraphs.AbstractGraph` type maintaining vertex numbering, even after vertex removal.
"""
struct VSafeGraph{T, G<:LG.AbstractGraph{T}, V<:AbstractVector{Int}} <: LG.AbstractGraph{T}
    g::G
    deleted_vertices::V
    VSafeGraph(g::G, v::V) where {T, G<:LG.AbstractGraph{T}, V<:AbstractVector{Int}} = new{T, G, V}(g, v)
end

VSafeGraph(g::G) where {G<:LG.AbstractGraph} = VSafeGraph(g, Vector{Int}())
VSafeGraph(nv::Integer) = VSafeGraph(LG.SimpleGraph(nv))

LG.edges(g::VSafeGraph) = LG.edges(g.g)
LG.edgetype(g::VSafeGraph) = LG.edgetype(g.g)

LG.is_directed(g::VSafeGraph) = LG.is_directed(g.g)
LG.is_directed(::Type{VSafeGraph}) = false
LG.is_directed(::Type{<:VSafeGraph{T,G}}) where {T, G} = LG.is_directed(G)

LG.ne(g::VSafeGraph) = LG.ne(g.g)
LG.nv(g::VSafeGraph) = LG.nv(g.g) - length(g.deleted_vertices)
LG.vertices(g::VSafeGraph) = (v for v in LG.vertices(g.g) if !(v in g.deleted_vertices))

LG.has_vertex(g::VSafeGraph, v) = LG.has_vertex(g.g, v) && !(v in g.deleted_vertices)

LG.has_edge(g::VSafeGraph, src, dst) = LG.has_edge(g.g, src, dst)
LG.has_edge(g::VSafeGraph, edge::LG.AbstractEdge) = LG.has_edge(g.g, LG.src(edge), LG.dst(edge))

LG.add_vertex!(g::VSafeGraph) = LG.add_vertex!(g.g)

LG.rem_edge!(g::VSafeGraph, v1, v2) = LG.rem_edge!(g.g, v1, v2)

Base.copy(g::VSafeGraph) = VSafeGraph(copy(g.g), copy(g.deleted_vertices))

function LG.outneighbors(g::VSafeGraph, v)
    if LG.has_vertex(g, v)
        LG.outneighbors(g.g, v)
    else
        throw(ArgumentError("$v is not a valid vertex in graph."))
    end
end

function LG.inneighbors(g::VSafeGraph, v)
    if LG.has_vertex(g, v)
        LG.inneighbors(g.g, v)
    else
        throw(ArgumentError("$v is not a valid vertex in graph."))
    end
end

function LG.add_edge!(g::VSafeGraph, v1, v2)
    if !LG.has_vertex(g, v1) || !LG.has_vertex(g, v2)
        return false
    end
    return LG.add_edge!(g.g, v1, v2)
end

LG.add_edge!(g::VSafeGraph, edge::LG.AbstractEdge) = LG.add_edge!(g, LG.src(edge), LG.dst(edge))

function LG.rem_vertex!(g::VSafeGraph, v1)
    if !LG.has_vertex(g, v1) || v1 in g.deleted_vertices
        return false
    end
    for v2 in LG.outneighbors(g, v1)
        LG.rem_edge!(g, v1, v2)
    end
    for v2 in LG.inneighbors(g, v1)
        LG.rem_edge!(g, v2, v1)
    end
    push!(g.deleted_vertices, v1)
    return true
end

end # module


module testUtils

using Base.Test
using DataFrames
using TimeData
include(string(Pkg.dir("AssetMgmt"), "/src/AssetMgmt.jl"))

println("\n Running utils tests\n")

testMus = [0.000399905;
           0.000883977;
           0.000967358;
           0.00215034 ;
           0.00132068 ;
           0.000903217;
           0.00130242 ;
           0.00116732 ;
           0.00245227 ;
           0.00213858 ;
           0.00135902 ;
           0.00142933 ;
           0.00117371 ;
           0.00228266 ;
           0.00116749 ];

testSigmas = [0.000317419;
              0.00363872 ;
              0.00512444 ;
              0.0175377  ;
              0.0165166  ;
              0.00427322 ;
              0.0159598  ;
              0.0265965  ;
              0.0255393  ;
              0.0304155  ;
              0.0288717  ;
              0.0336391  ;
              0.0311551  ;
              0.0259919  ;
              0.023314];

############################
## grossMomentsLogMoments ##
############################

## transformation from gross to log to gross should be identity

nAss = size(testMus, 1)
for ii=1:nAss
    muGross = 1 + testMus[ii]
    sigma = sqrt(testSigmas[ii])

    ## to log moments
    muLog, sigmaLog =
        AssetMgmt.grossRetMomentsToLogRetMoments(muGross, sigma)

    ## and back
    muGrossOut, sigmaOut =
        AssetMgmt.logRetMomentsToGrossRetMoments(muLog, sigmaLog)

    @test muGross == muGrossOut
    @test_approx_eq_eps sigma sigmaOut 1e-14
end

#######################
## scaling functions ##
#######################

## at least shouldn't throw errors
scaledMus, scaledSigmas =
    AssetMgmt.defaultMuSigmaScaling(testMus, testSigmas)

##############################
## symbol string conversion ##
##############################

testSymbs = [:hello; :world]
expOut = ["hello"; "world"]
actOut = AssetMgmt.symbToStr(testSymbs)
@test expOut == actOut

testStr = UTF8String["hello"; "world"]
expOut = [:hello; :world]
actOut = AssetMgmt.strToSymb(testStr)
@test actOut == expOut

## EMACS_STOPPER_EMACS_STOPPER_EMACS_STOPPER

##############
## old code ##
##############

## ##################
## ## column means ##
## ##################

## df = DataFrame(rand(4, 2))
## tmp = AssetMgmt.mean(df, 1)
## @test isa(tmp, DataFrame)

## df = DataFrame(rand(4, 1))
## AssetMgmt.mean(df, 1)

## df = DataFrame(rand(1, 8))
## AssetMgmt.mean(df, 1)

## ## throw error for row sums
## @test_throws AssetMgmt.mean(df, 2)

## df = DataFrame(rand(50, 4))
## tmp = AssetMgmt.cov(df)
## @test isa(tmp, DataFrame)

## #########
## ## cov ##
## #########

## covMatr = AssetMgmt.cov(df)

## #############
## ## corrcov ##
## #############

## AssetMgmt.corrcov(covMatr)

## ##############
## ## randWgts ##
## ##############

## wgts = AssetMgmt.randWgts(10, 8)
## AssetMgmt.chkEqualsOne(wgts)

## ######################
## ## composeDataFrame ##
## ######################

## df = DataFrame(rand(8, 4))
## vals = array(df)
## nams = names(df)
## rename!(df, names(df), [:a1 :a2 :a3 :a4])
## df2 = AssetMgmt.composeDataFrame(vals, [:a1 :a2 :a3 :a4])
## @test (df == df2)

## ###############################################
## ## check matching investment and return data ##
## ###############################################

## vals = AssetMgmt.randWgts(10, 5)
## inv = AssetMgmt.Investments(DataFrame(vals))
## rets = Timematr(DataFrame(rand(10, 5)))
## AssetMgmt.chkMatchInvData(inv, rets)

## ## test invalid names
## rets = Timematr(rand(10, 5), [:a1 :a2 :a3 :a4 :a5])
## @test_throws AssetMgmt.chkMatchInvData(inv, rets)

## ## test invalid index
## vals = AssetMgmt.randWgts(10, 5)
## inv = AssetMgmt.Investments(DataFrame(vals), [20:29])
## rets = Timematr(rand(10, 5))
## @test_throws AssetMgmt.chkMatchInvData(inv, rets)

end


using Images

function fitness(img, orig_img)
    ssdn(img, orig_img)
end


module COBRAUtils

include("io.jl")
include("manipulations.jl")
include("fba.jl")
include("AV.jl")
include("lp-problem.jl")

include("Media.jl")

end # module


@kwdef mutable struct DSubHandler <: AbstractHandler
    id = 0
end

@register(DSubHandler, -, [DBit, DBit] => [Number])

function (handler::DSubHandler)(netlist::Netlist,
                                inputs::Vector{Variable},
                                outputs::Vector{Variable})
    # check size
    if getsize(netlist, getname(inputs[1])) != (1, 1) && getsize(netlist, getname(inputs[1])) != (1, 1)
        error("Deterministic subtract does not support matrix hardware generation yet.")
    end

    outstring = """
        $stdcomment
        // BEGIN d_sub$(handler.id)
        determ_sub #(
                .BIT_WIDTH(BIT_WIDTH),
                .INT_WIDTH(INT_WIDTH)
            ) d_sub$(handler.id) (
                .a($(inputs[1].name)),
                .b($(inputs[2].name)),
                .y($(outputs[1].name))
            );
        // END d_sub$(handler.id)
        \n"""

    handler.id += 1

    return outstring
end

@kwdef mutable struct DSubAHandler <: AbstractHandler
    id = 0
end

@register(DSubAHandler, -, [DBit, Number] => [Number])

function (handler::DSubAHandler)(netlist::Netlist,
                                 inputs::Vector{Variable},
                                 outputs::Vector{Variable})
    # check size
    if getsize(netlist, getname(inputs[1])) != (1, 1) && getsize(netlist, getname(inputs[1])) != (1, 1)
        error("Deterministic subtract does not support matrix hardware generation yet.")
    end

    outstring = """
        $stdcomment
        // BEGIN d_suba_fxp$(handler.id)
        determ_suba_fxp #(
                .BIT_WIDTH(BIT_WIDTH),
                .INT_WIDTH(INT_WIDTH)
            ) d_suba_fxp$(handler.id) (
                .a($(inputs[1].name)),
                .b($(inputs[2].name)),
                .y($(outputs[1].name))
            );
        // END d_suba_fxp$(handler.id)
        \n"""

    handler.id += 1

    return outstring
end

@kwdef mutable struct DSubBHandler <: AbstractHandler
    id = 0
end

@register(DSubBHandler, -, [Number, DBit] => [Number])

function (handler::DSubBHandler)(netlist::Netlist,
                                 inputs::Vector{Variable},
                                 outputs::Vector{Variable})
    # check size
    if getsize(netlist, getname(inputs[1])) != (1, 1) && getsize(netlist, getname(inputs[1])) != (1, 1)
        error("Deterministic subtract does not support matrix hardware generation yet.")
    end

    outstring = """
        $stdcomment
        // BEGIN d_subb_fxp$(handler.id)
        determ_subb_fxp #(
                .BIT_WIDTH(BIT_WIDTH),
                .INT_WIDTH(INT_WIDTH)
            ) d_subb_fxp$(handler.id) (
                .a($(inputs[1].name)),
                .b($(inputs[2].name)),
                .y($(outputs[1].name))
            );
        // END d_subb_fxp$(handler.id)
        \n"""

    handler.id += 1

    return outstring
end


mutable struct Episode{E<:AbstractEnvironment,P<:AbstractPolicy,F<:AbstractFloat}
  env::E
  policy::P
  total_reward::F # total reward of the episode
  last_reward::F
  niter::Int      # current step in this episode
  freq::Int       # number of steps between choosing actions
  maxn::Int       # max steps in an episode - should be constant during an episode
end

Episode(env::AbstractEnvironment, π::AbstractPolicy; freq = 1, maxn = maxsteps(env)) =
  Episode(env, π, 0.0, 0.0, 1, freq, maxn)

function _start!(ep::Episode{E,P,F}) where {E,P,F}
  reset!(ep.env)
  reset!(ep.policy)
  ep.total_reward = zero(F)
  ep.niter = 1
end

_done(ep::Episode, i) =
  (ep.maxn != 0 && ep.niter >= ep.maxn) || finished(ep.env, state(ep.env))

# take one step in the enviroment after querying the policy for an action
function Base.iterate(ep::Episode, i = _start!(ep))
  _done(ep::Episode, i) && return nothing

  env = ep.env
  π = ep.policy
  s = state(env)
  A = actions(env, s)  # action space
  r = reward(env)
  a = action(π, r, s, A)

  @assert(a ∈ A, "action $a is not in $A")

  # take freq steps using action a
  last_reward = 0.0
  s′ = s
  for _ ∈ 1:ep.freq
    r, s′ = step!(env, s′, a)
    last_reward += r
    _done(ep, ep.niter) && break
  end

  ep.total_reward += last_reward
  ep.last_reward = last_reward
  ep.niter = i

  (s, a, r, s′), i + 1
end

"""
    run_episode(f, env, policy)
    run_episode(env, policy) do (s, a, r, s′)
      # render or something else
    end

Helper function for running an episode,
and return the total reward gained in this episode.
"""
function run_episode(f::Base.Callable, env::AbstractEnvironment, π::AbstractPolicy)
  ep = Episode(env, π)
  for sars in ep
    f(sars)
  end
  ep.total_reward
end

# ---------------------------------------------------------------------
# iterate through many episodes

mutable struct Episodes
  env
  kw

  # note: we have different groups of strategies depending on when they should be applied
  episode_strats    # learning strategies for each episode
  epoch_strats      # learning strategies for each complete episode
  iter_strats       # learning strategies applied at every iteration
end

function Episodes(env;
                  episode_strats = [],
                  epoch_strats = [],
                  iter_strats = [],
                  kw...)
  Episodes(
    env,
    kw,
    MetaLearner(episode_strats...),
    MetaLearner(epoch_strats...),
    MetaLearner(iter_strats...)
  )
end

length_state(eps::Episodes) = length(state(eps.env)) + length(eps.last_action)

# the main function... run episodes until stopped by one of the epoch/iter strats
function learn!(policy, eps::Episodes)
  # setup
  setup!(eps.epoch_strats, policy)
  setup!(eps.iter_strats, policy)

  # loop over epochs until done
  done = false
  epoch = 1
  iter = 1

  while !done
    # one episode
    setup!(eps.episode_strats, policy)
    ep = Episode(eps.env, policy; eps.kw...)
    for sars′ in ep
      learn!(policy, sars′...)

      # learn steps
      for metalearner in (eps.episode_strats, eps.epoch_strats, eps.iter_strats)
        for strat in metalearner.managers
          learn!(policy, strat, sars′)
        end
      end

      # iter steps
      timestep = ep.niter
      hook(eps.episode_strats, ep, timestep)
      hook(eps.epoch_strats, ep, epoch)
      hook(eps.iter_strats, ep, iter)

      # finish the timestep with checks
      if finished(eps.episode_strats, policy, timestep)
        break
      end
      if finished(eps.epoch_strats, policy, epoch) || finished(eps.iter_strats, policy, iter)
        done = true
        break
      end
      iter += 1
    end
    info("Finished episode $epoch after $(ep.niter) steps. Reward: $(ep.total_reward) mean(Reward): $(ep.total_reward/max(ep.niter,1))")
    cleanup!(eps.episode_strats, policy)
    epoch += 1

  end

  # tear down
  cleanup!(eps.epoch_strats, policy)
  cleanup!(eps.iter_strats, policy)
  return
end

# function hook(policy, ep::Episodes, i)
#     if ep.should_reset
#         reset!(ep.env)
#         reset!(policy)
#         ep.should_reset = false
#         ep.total_reward = 0.0
#         ep.nsteps = 0
#         for learner in ep.learners
#             setup!(learner, policy)
#         end
#     end
#
#     # take one step in the enviroment after querying the policy for an action
# 	env = ep.env
# 	s = state(env)
#     A = actions(env, s)
#     r = reward(env)
# 	a = action(policy, r, s, A)
#     if !(a in A)
#         warn("action $a is not in $A")
#         # a = rand(A)
#     end
#     @assert a in A
# 	r, s′ = step!(env, s, a)
# 	ep.total_reward += r
#
#     # "sars" learn step for the policy...
#     #   note: ensures that the final reward is included in the learning
#     learn!(policy, s, a, r, s′)
#
#     ep.nsteps += 1
#     for learner in ep.learners
#         learn!(policy, learner, ep.nsteps)
#         hook(learner, policy, ep.nsteps)
#     end
#
#     # if this episode is done, just flag it so we reset next time
#     if finished(env, s′) || any(learner -> finished(learner, policy, ep.nsteps), ep.learners)
#         ep.should_reset = true
#         ep.nepisode += 1
#         for learner in ep.learners
#             cleanup!(learner, policy)
#         end
#         info("Finished episode $(ep.nepisode) after $(ep.nsteps) steps. Reward: $(ep.total_reward)")
#     end
#     return
# end


@testset "cvt" begin
    @printf("\n> cvt\n")

    # toInt
    @test Bb.toInt("1") == UInt64(1)
    @test Bb.toInt("10") == UInt64(2)
    @test Bb.toInt("11") == UInt64(3)

    # PGN2UINT
    a8uint = Bb.PGN2UINT["a8"]
    @test Bb.UINT2PGN[a8uint] == "a8"

    # PGN2INT
    @test Bb.PGN2INT["a8"] == 1
    @test Bb.INT2PGN[1] == "a8"

    # INT2UINT
    @test Bb.INT2UINT[1] == Bb.PGN2UINT["a8"]
    uint = Bb.PGN2UINT["a8"]
    @test Bb.UINT2INT[uint] == 1
end
p2u = Bb.PGN2UINT


module Voronoi

include("Geometry.jl")
include("Diagram.jl")
include("EventQueue.jl")
include("BeachLine.jl")
include("Fortune.jl")
include("Intersect.jl")
include("Optimization.jl")

end


# function draw!(scene::Scene, h::OpticSim.ParaxialAnalysis.HeadEye.Head; draw_head=true, kwargs...) where {T<:Real}

#     # axes_mat = OpticSimVis.Material(color=RGBA(0.7, 0.2, 0.1, 1.0))
#     # axes = OpticSimVis.Axes(tr=Transform(Vec3(3.0, 3.0, 3.0)), material=axes_mat, name="My Axes")
#     # OpticSimVis.parent!(axes , OpticSimVis.root(scene))

#     name = "HeadEye" #-$(UUIDs.uuid1())"
#     mat = Material(;kwargs...)
#     # head_root = EmptySceneObject(material=mat, name=name)
#     head_root = Axes(
#         tr=transform(OpticSim.ParaxialAnalysis.HeadEye.tr(h)),
#         material=mat,
#         shaft_scale=0.4,
#         axes_scale=10.0,
#         name=name)
#     OpticSimVis.parent!(head_root , OpticSimVis.root(scene))

#     if (draw_head)
#         scale_factor = 60.0;
#         # face = ParaxialAnalysis.HeadEye.transform_mesh(ParaxialAnalysis.HeadEye.get_face_model(), ParaxialAnalysis.HeadEye.tr(h)*Geometry.scale(scale_factor, scale_factor, scale_factor))
#         face_mat = Material(color=RGBA(1.0,0.2,0.2, 0.3))
#         face = ParaxialAnalysis.HeadEye.transform_mesh(ParaxialAnalysis.HeadEye.get_face_model(), Geometry.scale(scale_factor, scale_factor, scale_factor))
#         face_mesh = Mesh(mesh=face, material=face_mat, name="Face")
#         OpticSimVis.parent!(face_mesh , head_root)
#     end

#     # ParaxialAnalysis.HeadEye.draw_local_frame(scene, ParaxialAnalysis.HeadEye.tr(h), "Head Transform")

#     # eyes
#     for eye in ParaxialAnalysis.HeadEye.eyes(h)
#         # Vis.draw!(scene, e, parent_transform = ParaxialAnalysis.HeadEye.tr(h))
#         local name = ParaxialAnalysis.HeadEye.text(eye)
#         eye_so = Axes(
#             tr=transform(OpticSim.ParaxialAnalysis.HeadEye.tr(eye)),
#             material=mat,
#             shaft_scale=0.2,
#             axes_scale=5.0,
#             name=name)
#         OpticSimVis.parent!(eye_so , head_root)

#         pupil = ParaxialAnalysis.HeadEye.pupil(eye)
#         name = "Pupil"
#         pupil_so = Axes(
#             tr=transform(OpticSim.ParaxialAnalysis.HeadEye.tr(pupil)),
#             material=mat,
#             shaft_scale=0.1,
#             axes_scale=2.0,
#             name=name)
#         OpticSimVis.parent!(pupil_so , eye_so)

#     end

#     return Dict{Symbol, Any}(
#         :scene => scene,
#         :scene_object => head_root,
#     )

# end


## Abstract interface for planners and solutions ##
export Planner

"Abstract planner type, which defines the interface for planners."
abstract type Planner end

(planner::Planner)(domain::Domain, state::State, spec) =
    solve(planner, domain, state, spec)

solve(planner::Planner, domain::Domain, state::State, spec::Specification) =
    error("Not implemented.")
solve(planner::Planner, domain::Domain, state::State, goals::Vector{<:Term}) =
    solve(planner, domain, state, Specification(goals))
solve(planner::Planner, domain::Domain, state::State, goal::Term) =
    solve(planner, domain, state, Specification(goal))

# Path-search planning algorithms
include("planners/path_search.jl")
include("planners/bfs.jl")
include("planners/forward.jl")
include("planners/backward.jl")

# Policy-based planning algorithms
include("planners/rtdp.jl")
include("planners/mcts.jl")

# External planners
include("planners/external.jl")


using Test

using QuantumControlBase
using QuantumControlBase.TestUtils

@testset "dummy optimization" begin

    println("")
    problem = dummy_control_problem()
    result = optimize(problem; method=:dummymethod)
    @test result.converged

end


using JSON
using OrderedCollections: OrderedDict
using DataStructures
using Printf
using StaticArrays
import YAML

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

logData = false

function ReadDataSet(filename::String)
    if logData
        printstyled("     Datafile: ", color=:green)
        printstyled(filename,"\n", color=:white)
    end

    if uppercase(filename[end-3:end]) == "JSON"
        f = open(filename, "r")
        dicttxt = read(f,String)
        close(f)
        DataDict=JSON.parse(dicttxt, dicttype=DataStructures.OrderedDict)
    elseif uppercase(filename[end-3:end]) == "YAML"
        DataDict = YAML.load_file(filename)
    else
        error("AUS und AUS")
    end

    setDict = OrderedDict{String, Any}()

    # OrderedDict für Reihenfolge wie in der Datei
    isZONE = DataDict["description"]["type"]=="ZONEdata"
    for (key, value) in DataDict
        if key=="description"; continue;    end
        valueDict = OrderedDict{Symbol, Any}()
        setDict[key] = valueDict
        for (key1, value1) in value
            valueDict[Symbol(key1)] = value1
        end
        if logData
            printstyled("  data set ID: ", color=:cyan)
            printstyled(key,"  ", color=:white)
            printstyled("(Prj: ",valueDict[:Project],")\n", color=:light_black)
        end
#         println()
        if isZONE
             valueDict[:Omegarated] = valueDict[:frated]*2.0*pi
             valueDict[:Speedrad] = valueDict[:Omegarated]/valueDict[:NoPolepairs]
             valueDict[:Irated] = valueDict[:Srated]/valueDict[:Vrated]/sqrt(3.0)
             valueDict[:Zbase] = valueDict[:Vrated]*valueDict[:Vrated]/valueDict[:Srated]
             valueDict[:Tbase] = valueDict[:Srated]/valueDict[:Speedrad]
             valueDict[:Speedrpm] = valueDict[:frated]*60.0/valueDict[:NoPolepairs]
        end
        valueDict[:_class] = :Map
    end
    println()
    return setDict
end

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

function Ergebnis(tttt, liste::Vector{String})
    printstyled(lpad("[signal name]",21), color=:light_green)
    printstyled("      [1]", color=:green)
    printstyled("    [end]", color=:cyan)
    printstyled("     [min : max] \n", color=:light_yellow)
    for w in liste
        if !haskey(tttt.result_info, w) && !haskey(tttt.evaluatedParameters, Symbol(w))
            printstyled(lpad(w,21),"\n", color=:red)
            continue
        end
        daten = get_result(tttt,w)
        L1 = length(daten)
        L2 = length(daten[1])
        RES = Array{Float64,2}(zeros(L1,L2))    # für MIN/MAX notwendig.
        for i = 1:L1
            for j = 1:L2
                RES[i,j] = daten[i][j]
            end
        end

        if L2 == 1
            printstyled(lpad(w,21), color=:light_green)
            printstyled(@sprintf("  %7.4g",RES[1]), color=:green)
            printstyled(@sprintf("  %7.4g",RES[end]), color=:cyan)
            printstyled(@sprintf("  %7.4g :%7.4g\n",minimum(RES),maximum(RES)), color=:light_yellow)
        else
            for iz =1:L2
                printstyled(lpad(w*"[$iz]",21), color=:light_green)
                printstyled(@sprintf("  %7.4g",RES[1,iz]), color=:green)
                printstyled(@sprintf("  %7.4g",RES[end,iz]), color=:cyan)
                printstyled(@sprintf("  %7.4g :%7.4g\n",minimum(RES,dims=1)[iz],maximum(RES,dims=1)[iz]), color=:light_yellow)
            end

        end
    end
end

function ergebnis(tttt, w::String)
    if length(w)==0
    else
        daten = get_result(tttt,w)
        datenlg = length(daten[1])
        if datenlg == 1
            printstyled(lpad(w,21), color=:light_green)
            printstyled(@sprintf("  %7.4g",daten[1]), color=:green)
            printstyled(@sprintf("  %7.4g",daten[end]), color=:cyan)
            printstyled(@sprintf("  %7.4g :%7.4g\n",minimum(daten),maximum(daten)), color=:light_yellow)
        else
            for iz =1:datenlg
                printstyled(lpad(w*"[$iz]",21), color=:light_green)
                printstyled(@sprintf("  %7.4g",daten[1][iz]), color=:green)
                printstyled(@sprintf("  %7.4g",daten[end][iz]), color=:cyan)
                printstyled("      coming soon\n", color=:light_black)
#                 printstyled(@sprintf("  %7.4g :%7.4g\n",minimum(daten[:][iz]),maximum(daten[:][iz])), color=:light_yellow)
            end

        end
    end
end

function Speichern(mod, liste::AbstractArray= [])

    datei = "myfile.txt"
    if length(liste)==0
        printstyled("Länge der Liste = 0!!\n", color=:red)
        return
    end
    df=get_result(mod)
    open(datei, "w") do io
        show(io, MIME("text/csv"), df[!,liste])
        printstyled(@sprintf("save CSV in file: %s with %d colums, %s rows\n",datei, length(liste),nrow(df)), color=:green)
    end
end

function TemplateInit(instModel; tempName="Start.Template.jl")::Nothing

    function StartWert(wert::Float64)::String
        return string(wert)
    end

    function StartWert(wert::SVector{2, Float64})::String   #FIXME Länge 2 ist angenommen
        w = "["
        lg = length(wert.data)
        for i= 1:lg
            w = w * string(wert.data[i])
            if i<lg
                w = w * ", "
            end
        end
        w = w * "]"
        return w
    end

    Etxt = "EIN = (\n"
    Atxt = "AUS = (\n"

    for Z in instModel.equationInfo.x_info

        TXT1 = "    (\"$(Z.x_name)\", " * StartWert(Z.startOrInit) * " ),\n"
        if Z.scalar
            TXT2 = "    (\"$(Z.der_x_name)\", 0.0),\n"
        elseif Z.length==2
            TXT2 = "    (\"$(Z.der_x_name)\", [0.0, 0.0]),\n"
        else
            printstyled("ERROR - $(Z_xname)  length >2\n", color=:red)
        end
        Etxt = Etxt * TXT1
        Atxt = Atxt * TXT2
    end

    Etxt = Etxt * ")\n\n\n"
    Atxt = Atxt * ")\n"

    Ausgabe = ""

    open(tempName, "w") do io
        print(io, Etxt)
        print(io, Atxt)
    end

    nothing

end


using Glob: glob
using Random: shuffle
using Distributed: procs, addprocs
using Distributed: @everywhere

const learning_rate = .01
const hm_epochs     = 50
const batch_size    = 2

const in_size       = 52
const layers        = [52]
const out_size      = 52

if length(procs()) <= 2
    hm_procs = batch_size > Sys.CPU_THREADS ?
        Sys.CPU_THREADS-1 : batch_size-1
    addprocs(hm_procs) ;println("$(length(procs())) cores running.")
end
@everywhere include("gru_api(char_to_char).jl")
@everywhere include("utils.jl")

create_model_definitions(layers)

const model = Model(mk_layers(in_size, layers, out_size)...)

const data = import_data(glob("class*.txt")[1])

for i in 1:hm_epochs
    print("epoch: $i ")
    train!(model, shuffle(data), learning_rate, batch_size)
    end
end

save_model(model)

print("Hit enter to continue..")
chomp(readline())


module MKLSparse

using MKL_jll

function __init__()
    ccall((:MKL_Set_Interface_Layer, libmkl_rt), Cint, (Cint,), Base.USE_BLAS64 ? 1 : 0)
end

include(joinpath("BLAS", "BLAS.jl"))

end # module


import Statistics: mean
using Printf
using DelimitedFiles
import Images
import ImageFiltering
using Base.Threads
using MultivariateStats
using Statistics
#data augmentation
"""
    flip_volume_front(x, y)

Flip a volume so that the longitudinal (x) axis becomes the vertical (z) axis
and adjust the landmakr coordinates accordingly.
"""
function flip_volume_front(x, y)
  newx = zeros(Float32, size(x,3),size(x,2),size(x,1),size(x,4))
  newy = deepcopy(y)
  Threads.@threads for ind in 1:size(y,2)
    for z in 1:size(x, 1)
      newx[:,:,z,ind] = transpose(x[z,:,:,ind])
    end
    for cor in 1:3:size(y,1)
      newy[cor, ind] = y[cor+2, ind]
      newy[cor+1, ind] = y[cor+1, ind]
      newy[cor+2, ind] = y[cor, ind]
    end
  end
  return newx, newy
end

"""
    flip_volume_side(x, y)

Flip a volume so that the lateral (y) axis becomes the vertical (z) axis
and adjust the landmark coordinates accordingly.
"""
function flip_volume_side(x, y)
  newx = zeros(Float32, size(x,2),size(x,3),size(x,2),size(x,4))
  newy = deepcopy(y)
  Threads.@threads for ind in 1:size(y,2)
    for z in 1:size(x, 2)
      newx[:,:,z,ind] = x[:,z,:,ind]
    end
    for cor in 1:3:size(y,1)
      newy[cor, ind] = y[cor, ind]
      newy[cor+1, ind] = y[cor+2, ind]
      newy[cor+2, ind] = y[cor+1, ind]
    end
  end
  return newx, newy
end

"""
    function mirror_vol(x, y)

A form of data augmentation. Mirrors a volume (exchanges x and y axis) and returns
concatenation of original data and mirrored data.
"""
function mirror_vol(x, y)
  copyx = x
  copyy = y
  copyx = cat(x, copyx, dims=length(size(x)))
  copyy = hcat(y, copyy)
  inds = size(x)[length(size(x))]
  for ind in 1:inds
    for slice in 1:size(x)[3]
      if length(size(x)) == 5
        trans = x[:,:,slice, 1, ind]
        copyx[:,:,slice, 1, ind+inds] = trans'
      else
        trans = x[:,:,slice, ind]
        copyx[:,:,slice, ind+inds] = trans'
      end
    end
    for cor in 1:3:size(y)[1]
      copyy[cor, ind+inds] = y[cor+1, ind]
      copyy[cor+1, ind+inds] = y[cor, ind]
    end
  end
  return copyx, copyy
end

"""
  flip_2D(x, y)

Takes 2D images in a 4D tensor and landmark data in 2D tensor and returns
the flipped (clockwise) images, aswell
as the coordinates for the flipped images. only works on square images.
"""
function flip_2D(x, y)
  copyx = zeros(Float32, size(x, 2),size(x, 1),size(x, 3),size(x, 4))
  copyy = deepcopy(y)
  inds = size(y, 2)
  size_x = size(x, 1)
  for i in 1:inds
    for cor in 1:2:size(y, 1)
      copyy[cor+1, i] = size(x,2)/10 - y[cor, i]
      copyy[cor, i] = y[cor+1, i]
    end
    for img in 1:size(x, 3)
      for o in 1:size_x
        copyx[:, size_x-(o-1), img, i] = x[o, :, img, i]
      end
    end
  end
  return copyx, copyy
end

"""
    flip_3D(x, y)

Takes 3D volumes in a 4D tensor and landmark data in 2D tensor and returns
the flipped (clockwise) volumes, aswell
as the coordinates for the flipped volumes.
"""
function flip_3D(x, y)
  copyx = zeros(Float32, size(x, 2),size(x, 1),size(x, 3),size(x, 4))
  copyy = deepcopy(y)
  inds = size(y, 2)
  size_x = size(x, 1)
  Threads.@threads for i in 1:inds
    for cor in 1:3:size(y, 1)
      copyy[cor+1, i] = size(x,2)/10 - y[cor, i]
      copyy[cor, i] = y[cor+1, i]
    end
    for o in 1:size_x
      copyx[:, size_x-(o-1), :, i] = x[o, :, :, i]
    end
  end
  return copyx, copyy
end

# """
#     jitter(x, y)
#
# Jitters around images based on their corresponding 2D landmarks, so that
# all landmarks and at least 8 pixels around them will still be inside
# the image.
# """
# function jitter(x, y)
#   copyx = zeros(Float32, size(x, 2),size(x, 1),size(x, 3),size(x, 4))
#   copyy = y
#   copyx = cat(x, copyx, dims=length(size(x)))
#   copyy = hcat(y, copyy)
#   inds = size(y, 2)
#   for i in 1:inds
#     max_x = 0
#     max_y = 0
#     for cor in 1:2:size(y,1)
#       if y[cor+1, i] > max_y
#         max_y = round(y[cor+1, i], digits=1)
#       end
#       if y[cor, i] > max_x
#         max_x = round(y[cor, i], digits=1)
#       end
#     end
#     max_x = max_x*10 + 8
#     max_y = max_y*10 + 8
#     minval = minimum(x[:,:,1,i])
#     if max_x >= size(x,1)
#       jitterx = 1
#     else
#       jitterx = rand(1:size(x,1)-floor(Int, max_x))
#       for o in 1:jitterx
#         copyx[o, :, 1, inds + i] .= minval
#       end
#     end
#     if max_y >= size(x,2)
#       jittery = 1
#     else
#       jittery = rand(1:size(x,2)-floor(Int, max_y))
#       for o in 1: jittery
#         copyx[:, o, 1, inds + i] .= minval
#       end
#     end
#     for o in jitterx:size(x,1)
#       copyx[o, jittery+1:end,1,inds+i] = x[(o-(jitterx-1)), 1:end-jittery,1,i]
#     end
#     for o in 1:2:size(y,1)
#       copyy[o,inds+i] = y[o,i] + jitterx/10
#       copyy[o+1,inds+i] = y[o+1,i] + jittery/10
#     end
#   end
#   return copyx, copyy
# end

"""
    jitter_2D(imgs, landmarks, padding)

Jitters around 2D images based on their landmarks. The landmarked part of the
images plus #(padding) pixels around them will not be moved out of the
picture. returns jittered images and landmarks.
"""
function jitter_2D(imgs, landmarks, padding)
  coordinates = to_3d_array_2d(landmarks)
  out = deepcopy(imgs)
  Threads.@threads for ind in 1:size(landmarks, 2)
    size_x = size(imgs, 1)
    size_y = size(imgs, 2)
    min_x = max(0, minimum(coordinates[:,1,ind]) * 10 - padding)
    max_x = min(size_x, maximum(coordinates[:,1,ind]) * 10 + padding)
    min_y = max(0, minimum(coordinates[:,2,ind]) * 10 - padding)
    max_y = min(size_y, maximum(coordinates[:,2,ind]) * 10 + padding)
    jitter_x = rand(-floor(Int, min_x):(size_x-floor(Int, max_x)))
    jitter_y = rand(-floor(Int, min_y):(size_y-floor(Int, max_y)))
    for img in 1:size(imgs, 3)
        filler = maximum(imgs[:,:,img,ind])
        out[:,:,img,ind] .= filler
        out[max(1, jitter_x+1):min(size_x, size_x+jitter_x), max(1, jitter_y+1):min(size_y, size_y+jitter_y),
          img, ind] .= imgs[max(1, -jitter_x+1):min(size_x, size_x-jitter_x),
          max(1, -jitter_y+1):min(size_y, size_y-jitter_y), img, ind]
    end
    coordinates[:,1,ind] .= coordinates[:,1,ind] .+ jitter_x/10
    coordinates[:,2,ind] .= coordinates[:,2,ind] .+ jitter_y/10
  end
  lms_out = to_2d_array_2d(coordinates)
  return out, lms_out
end

"""
    jitter_3D(volumes, landmarks, padding)

Jitters around binarized volumes based on their corresponding x/y landmarks, so that
the relevant object (smaller voxel value) will still be fully inside the volume but
moved around randomly inside the volume.
"""
function jitter_3D(volumes, landmarks, padding)
  coordinates = to_3d_array(landmarks)
  out = deepcopy(volumes)
  Threads.@threads for ind in 1:size(landmarks, 2)
    filler = maximum(volumes[:,:,:,ind])
    size_x = size(volumes[:,:,:,ind], 1)
    size_y = size(volumes[:,:,:,ind], 2)
    size_z = size(volumes[:,:,:,ind], 3)
    min_x = max(0, minimum(coordinates[:,1,ind]) * 10 - padding)
    max_x = min(size_x, maximum(coordinates[:,1,ind]) * 10 + padding)
    min_y = max(0, minimum(coordinates[:,2,ind]) * 10 - padding)
    max_y = min(size_y, maximum(coordinates[:,2,ind]) * 10 + padding)
    min_z = max(0, minimum(coordinates[:,3,ind]) * 10 - padding)
    max_z = min(size_z, maximum(coordinates[:,3,ind]) * 10 + padding)
    jitter_x = rand(-floor(Int, min_x):(size_x-floor(Int, max_x)))
    jitter_y = rand(-floor(Int, min_y):(size_y-floor(Int, max_y)))
    jitter_z = rand(-floor(Int, min_z):(size_z-floor(Int, max_z)))
    out[:,:,:,ind] .= filler
    out[max(1, jitter_x+1):min(size_x, size_x+jitter_x), max(1, jitter_y+1):min(size_y, size_y+jitter_y),
      max(1, jitter_z+1):min(size_z, size_z+jitter_z), ind] .= volumes[max(1, -jitter_x+1):min(size_x, size_x-jitter_x),
      max(1, -jitter_y+1):min(size_y, size_y-jitter_y),
        max(1, -jitter_z+1):min(size_z, size_z-jitter_z), ind]
    coordinates[:,1,ind] .= coordinates[:,1,ind] .+ jitter_x/10
    coordinates[:,2,ind] .= coordinates[:,2,ind] .+ jitter_y/10
    coordinates[:,3,ind] .= coordinates[:,3,ind] .+ jitter_z/10
  end
  lms_out = to_2d_array(coordinates)
  return out, lms_out
end

"""
    rotate_2d(img, lms, deg)

Rotates an Image by deg degrees around its center in a counter clockwise direction.
The 2d landmarks (coordinates x individuals) will be adjusted to the rotated image.
Returns rotated image and adjusted landmarks.
"""
function rotate_2d(img, lms, deg)
  deg = deg*pi/180
  rotated = Images.imrotate(img, -deg)
  size_x = size(rotated, 1)
  size_y = size(rotated, 2)
  out = Images.imresize(rotated, (size(img, 1), size(img, 2)))
  n_points = floor(Int, length(lms)/2)
  coords = zeros(2,n_points)
  coords_out = zeros(n_points*2, 1)
  for i in 1:n_points
    coords[1,i] = lms[i*2-1]
    coords[2,i] = lms[i*2]
  end
  coords[1,:] = coords[1,:] .- (size(img,1)/2)/10
  coords[2,:] = coords[2,:] .- (size(img,2)/2)/10
  for i in 1:n_points
    coords_out[i*2-1] = (coords[1,i]*cos(deg) - coords[2,i]*sin(deg) + (size_x/2)/10) * size(img,1)/size_x
    coords_out[i*2] = (coords[1,i]*sin(deg) + coords[2,i]*cos(deg) + (size_y/2)/10) * size(img,2)/size_y
  end
  return out, coords_out
end

"""
    rotate_images(img, lms, deg)

Takes a 3d tensor [res1 x res1 x n] with n images and rotates all of them
in a counterclockwise direction around their center by deg degrees. Will adjust the 2d coordinates
of the landmark array [coords x n]. Returns rotated images and adjusted landmarks.
"""
function rotate_images(imgs, lms, deg)
  n_inds = size(imgs, 4)
  out = deepcopy(imgs)
  lms_out = deepcopy(lms)
  for i in 1:n_inds
    for img in 1:size(imgs, 3)
      filler = maximum(imgs[:,:,img,i])
      if img == 1
        rotated, rotated_lms = rotate_2d(imgs[:,:,img,i], lms[:,i], deg)
        lms_out[:,i:i] .= rotated_lms
        rotated[findall(x->isnan(x)==true, rotated)] .= filler
      else
        rotated_img = Images.imrotate(imgs[:,:,img,i], -deg*pi/180)
        rotated = Images.imresize(rotated_img, (size(imgs, 1), size(imgs, 2)))
        rotated[findall(x->isnan(x)==true, rotated)] .= filler
      end
      out[:,:,img,i] .= rotated

    end
  end
  return out, lms_out
end

"""
    rotate_volumes(vols, lms, deg)

Takes a 4d tensor [res1 x res2 x res3 x n] with n volumes and rotates all of them
around their center by deg degrees. The rotation is along the z-axis, so the z-coordinates
will not be affected. x- and y coordinates of the landmark array [coords x n] will be adjusted.
Returns rotated volumes and adjusted landmarks.
"""
function rotate_volumes(vols, lms, deg)
  n_inds = size(lms, 2)
  n_points = floor(Int, size(lms, 1)/3)
  x_y_only = zeros(n_points*2, n_inds)
  for i in 1:n_points
    x_y_only[i*2-1:i*2,:] .= lms[i*3-2:i*3-1,:]
  end
  x_y_rotated = deepcopy(x_y_only)
  out = deepcopy(vols)
  out_lms = deepcopy(lms)
  for i in 1:n_inds
    filler = maximum(vols[:,:,:,i])
    for img in 1:size(vols, 3)
      if img == 1
        rotated, rotated_lms = rotate_2d(vols[:,:,img,i], x_y_only[:,i], deg)
        rotated[findall(x->isnan(x)==true, rotated)] .= filler
        out[:,:,img,i] .= rotated
        x_y_rotated[:,i] .= rotated_lms[:,1]
      else
        rotated_img = Images.imrotate(vols[:,:,img,i], -deg*pi/180)
        rotated = Images.imresize(rotated_img, (size(vols, 1), size(vols, 2)))
        rotated[findall(x->isnan(x)==true, rotated)] .= filler
        out[:,:,img,i] .= rotated
      end
    end
  end
  for i in 1:n_points
    out_lms[i*3-2:i*3-1,:] .= x_y_rotated[i*2-1:i*2,:]
  end
  return out, out_lms
end


"""
The geographical location of a node of the supply chain.
The location is defined by its latitude and longitude.
"""
struct Location
    latitude::Float64
    longitude::Float64
    name

    function Location(latitude, longitude)
        return new(latitude, longitude, nothing)
    end

    function Location(latitude, longitude, name)
        return new(latitude, longitude, name)
    end
end

"""
A node of the supply chain.
"""
abstract type Node
end

"""
A product.
"""
struct Product
    name::String
    unit_holding_cost::Float64

    """
    Creates a new product.
    """
    function Product(name::String; unit_holding_cost::Float64=0.0)
        return new(name, unit_holding_cost)
    end
end

Base.:(==)(x::Product, y::Product) = x.name == y.name
Base.hash(x::Product, h::UInt64) = hash(x.name, h)
Base.show(io::IO, x::Product) = print(io, x.name)

"""
A transportation lane between two nodes of the supply chain.
"""
struct Lane
    origin::Node
    destination::Node
    unit_cost::Float64
    minimum_quantity::Float64
    time::Int

    function Lane(origin, destination; unit_cost=0.0, minimum_quantity=0.0, time::Int=0)
        return new(origin, destination, unit_cost, minimum_quantity, time)
    end
end

"""
A customer.
"""
struct Customer <: Node
    name::String

    location::Location

    function Customer(name::String, location::Location)
        return new(name, location)
    end
end

"""
    add_product!(customer::Customer, product::Product; demand::Array{Float64, 1})

Indicates that a customer needs a product.

The keyword arguments are:
 - `demand`: the amount of unit of product needed for each time period.

"""
function add_product!(customer::Customer, product::Product; demand::Array{Float64, 1})
    customer.demand[product] = demand
end

Base.:(==)(x::Customer, y::Customer) = x.name == y.name
Base.hash(x::Customer, h::UInt64) = hash(x.name, h)
Base.show(io::IO, x::Customer) = print(io, x.name)

"""
A supplier.
"""
struct Supplier <: Node
    name::String

    unit_cost::Dict{Product, Float64}

    maximum_throughput::Dict{Product, Float64}

    location::Location

    """
    Creates a new supplier.
    """
    function Supplier(name::String, location::Location)
        return new(name, Dict{Product, Float64}(), Dict{Product, Float64}(), location)
    end
end

"""
    add_product!(supplier::Supplier, product::Product; unit_cost::Float64, maximum_throughput::Float64)

Indicates that a supplier can provide a product.

The keyword arguments are:
 - `unit_cost`: the cost per unit of the product from this supplier.
 - `maximum_throughput`: the maximum number of units that can be provided in each time period.

"""
function add_product!(supplier::Supplier, product; unit_cost::Real, maximum_throughput::Real=Inf)
    supplier.unit_cost[product] = unit_cost
    supplier.maximum_throughput[product] = maximum_throughput
end

Base.:(==)(x::Supplier, y::Supplier) = x.name == y.name
Base.hash(x::Supplier, h::UInt64) = hash(x.name, h)
Base.show(io::IO, x::Supplier) = print(io, x.name)

"""
A storage location.
"""
struct Storage <: Node
    name::String

    fixed_cost::Float64

    opening_cost::Float64
    closing_cost::Float64

    initial_opened::Bool
    initial_inventory::Dict{Product, Float64}

    unit_handling_cost::Dict{Product, Float64}

    maximum_throughput::Dict{Product, Float64}

    additional_stock_cover::Dict{Product, Float64}

    location::Location

    """
    Creates a new storage location.
    """
    function Storage(name::String, location::Location; fixed_cost::Real=0.0, opening_cost::Real=0.0, closing_cost::Real=Inf,
                     initial_opened::Bool=true)
        return new(name,
                   fixed_cost, opening_cost, closing_cost,
                   initial_opened,
                   Dict{Product, Float64}(), Dict{Product, Float64}(), Dict{Product, Float64}(), Dict{Product, Float64}(),
                   location)
    end
end

function add_product!(storage::Storage, product; initial_inventory::Union{Real, Nothing}=0, unit_handling_cost::Real=0, maximum_throughput::Real=Inf, additional_stock_cover::Real=0.0)
    if !isnothing(initial_inventory)
        storage.initial_inventory[product] = initial_inventory
    end
    storage.unit_handling_cost[product] = unit_handling_cost
    storage.maximum_throughput[product] = maximum_throughput
    storage.additional_stock_cover[product] = additional_stock_cover
end

Base.:(==)(x::Storage, y::Storage) = x.name == y.name
Base.hash(x::Storage, h::UInt64) = hash(x.name, h)
Base.show(io::IO, x::Storage) = print(io, x.name)

"""
A plant.
"""
struct Plant <: Node
    name::String

    fixed_cost::Float64

    opening_cost::Float64
    closing_cost::Float64

    initial_opened::Bool

    bill_of_material::Dict{Product, Dict{Product, Float64}}
    unit_cost::Dict{Product, Float64}
    time::Dict{Product, Int}

    maximum_throughput::Dict{Product, Float64}

    location::Location

    """
    Creates a new plant.
    """
    function Plant(name::String, location::Location; fixed_cost::Real=0.0, opening_cost::Real=0.0, closing_cost::Real=Inf, initial_opened::Bool=true)
        return new(name, fixed_cost, opening_cost, closing_cost, initial_opened, Dict{Product, Dict{Product, Float64}}(), Dict{Product, Float64}(), Dict{Product, Float64}(), Dict{Product, Float64}(), location)
    end
end

"""
    add_product!(plant::Plant, product::Product; bill_of_material::Dict{Product, Float64}, unit_cost, maximum_throughput)

Indicates that a plant can produce a product.

The keyword arguments are:
 - `bill_of_material`: the amount of other product needed to produce one unit of the product. This dictionary can be empty if there are no other products needed.
 - `unit_cost`: the cost of producing one unit of product.
 - `maximum_throughput`: the maximum amount of product that can be produced in a time period.
 - `time`: the production lead time.

"""
function add_product!(plant::Plant, product; bill_of_material::Dict{Product, Float64}, unit_cost, maximum_throughput::Real=Inf, time::Int=0)
    plant.bill_of_material[product] = bill_of_material
    plant.unit_cost[product] = unit_cost
    plant.maximum_throughput[product] = maximum_throughput
    plant.time[product] = time
end

Base.:(==)(x::Plant, y::Plant) = x.name == y.name
Base.hash(x::Plant, h::UInt64) = hash(x.name, h)
Base.show(io::IO, x::Plant) = print(io, x.name)

"""
The demand a customer has for a product.
"""
struct Demand
    customer::Customer
    product::Product
    probability::Float64
    demand::Array{Float64, 1}
    service_level::Float64

    function Demand(customer::Customer, product::Product, demand::Array{Float64, 1}, service_level)
        return new(customer, product, 1.0, demand, service_level)
    end
end

"""
The supply chain.
"""
mutable struct SupplyChain
    horizon::Int
    products::Set{Product}
    storages::Set{Storage}
    suppliers::Set{Supplier}
    customers::Set{Customer}
    plants::Set{Plant}
    lanes::Set{Lane}
    demand::Set{Demand}

    lanes_in::Dict{Node, Array{Lane, 1}}
    lanes_out::Dict{Node, Array{Lane, 1}}

    optimization_model

    """
    Creates a new supply chain.
    """
    function SupplyChain(horizon=1)
        sc = new(horizon,
                 Set{Product}(),
                 Set{Storage}(),
                 Set{Supplier}(),
                 Set{Customer}(),
                 Set{Plant}(),
                 Set{Lane}(),
                 Set{Demand}(),
                 Dict{Node, Array{Lane, 1}}(),
                 Dict{Node, Array{Lane, 1}}(),
                 nothing)
        return sc
    end
end

"""
    add_demand!(supply_chain, customer, product; demand::Array{Float64, 1}, service_level=1.0)

Adds customer demand for a product. The demand is specified for each time period.

The keyword arguments are:
 - `demand`: the amount of product demanded for each time period.
 - `service_level`: indicates how many lost sales are allowed as a ratio of demand. No demand can be lost if the service level is 1.0 and all demand can be lost if the service level is 0.0.

"""
function add_demand!(supply_chain, customer, product; demand::Array{Float64, 1}, service_level=1.0)
    if service_level < 0.0 || service_level > 1.0
        throw(DomainError("service_level must be between 0.0 and 1.0 inclusive"))
    end
    add_demand!(supply_chain, Demand(customer, product, demand, service_level))
end

"""
    add_demand!(supply_chain, demand)

Adds demand to the supply chain.
"""
function add_demand!(supply_chain, demand)
    push!(supply_chain.demand, demand)
end

"""
    add_product!(supply_chain, product)

Adds a product to the supply chain.
"""
function add_product!(supply_chain, product)
    push!(supply_chain.products, product)
    return product
end

"""
    add_customer!(supply_chain, customer)

Adds a customer to the supply chain.
"""
function add_customer!(supply_chain, customer)
    push!(supply_chain.customers, customer)
    return customer
end

"""
    add_supplier!(supply_chain, supplier)

Adds a supplier to the supply chain.
"""
function add_supplier!(supply_chain, supplier)
    push!(supply_chain.suppliers, supplier)
    return supplier
end

"""
    add_storage!(supply_chain, storage)

Adds a storage location to the supply chain.
"""
function add_storage!(supply_chain, storage)
    push!(supply_chain.storages, storage)
    return storage
end

"""
    add_plant!(supply_chain, plant)

Adds a plant to the supply chain.
"""
function add_plant!(supply_chain, plant)
    push!(supply_chain.plants, plant)
    return plant
end

"""
    add_lane!(supply_chain, lane)

Adds a transportation lane to the supply chain.
"""
function add_lane!(supply_chain, lane)
    push!(supply_chain.lanes, lane)

    if !haskey(supply_chain.lanes_in, lane.destination)
        supply_chain.lanes_in[lane.destination] = Array{Lane, 1}()
    end
    push!(supply_chain.lanes_in[lane.destination] , lane)

    if !haskey(supply_chain.lanes_out, lane.origin)
        supply_chain.lanes_out[lane.origin] = Array{Lane, 1}()
    end
    push!(supply_chain.lanes_out[lane.origin] , lane)
    return lane
end


abstract type AbstractBoosting <: AbstractPolicy end

# function boost!(boosting::AbstractBoosting,gprev::AbstractVector,gcurr::AbstractVector)
#     error("No defined boost! function for boosting policy ", typeof(boosting))
# end

function boost!() end

function boost_wrapper(wid::Cint,klocal::Cint,kglobal::Cint,gprev_b::Ptr{Cdouble},gprev_e::Ptr{Cdouble},gcurr_b::Ptr{Cdouble},boost_data::Ptr{Cvoid})
    boost_policy = unsafe_pointer_to_objref(boost_data)::AbstractBoosting
    ptrdiff = Int(gprev_e - gprev_b)
    N = divrem(ptrdiff, sizeof(Cdouble))[1]
    gprev = unsafe_wrap(Array, gprev_b, N)
    gcurr = unsafe_wrap(Array, gcurr_b, N)
    boost!(boost_policy, wid, klocal, kglobal, gprev, gcurr)
    return gcurr_b + ptrdiff
end

module Boosting

using Parameters
using POLO: AbstractBoosting, AbstractPolicyParameters
import POLO: initialize!, boost!

function initialize!(policy::AbstractBoosting,x₀::Vector{Float64})
    nothing
end

include("none.jl")
include("momentum.jl")

end


immutable FloatRect
    left::Cfloat
    top::Cfloat
    width::Cfloat
    height::Cfloat
end

immutable IntRect
    left::Cint
    top::Cint
    width::Cint
    height::Cint
end

function contains(rect::FloatRect, x::Real, y::Real)
    rectPtr = Ref{FloatRect}(rect)
    return ccall((:sfFloatRect_contains, libcsfml_graphics), Int32, (Ref{FloatRect}, Cfloat, Cfloat,), (rectPtr), x, y) == 1
end

function contains(rect::IntRect, x::Int, y::Int)
    rectPtr = Ref{FloatRect}(rect)
    return ccall((:sfIntRect_contains, libcsfml_graphics), Int32, (Ref{IntRect}, Cint, Cint,), (rectPtr), x, y) == 1
end

function intersects(rect1::FloatRect, rect2::FloatRect)
    rect1Ptr = Ref{FloatRect}(rect1)
    rect2Ptr = Ref{FloatRect}(rect2)
    return Bool(ccall((:sfFloatRect_intersects, libcsfml_graphics), Int32, (Ref{FloatRect}, Ref{FloatRect}, Ptr{FloatRect},), (rect1Ptr), (rect2Ptr), C_NULL))
end

function intersects(rect1::IntRect, rect2::IntRect)
    rect1Ptr = Ref{FloatRect}(rect1)
    rect2Ptr = Ref{FloatRect}(rect2)
    return ccall((:sfIntRect_intersects, libcsfml_graphics), Int32, (Ref{IntRect}, Ref{IntRect}, Ptr{IntRect},), (rect1Ptr), (rect2Ptr), C_NULL) == 1
end