module Nektar
using Mesh2d

type NektarBndry <: Mesh2d.Bndry
    tag::Char
    elem::Int
    edge::Int
    params::Array{Float64,1}
    funs::Array{String, 1}
    NektarBndry(bt, el, ed, p, funs=Array(String,0)) = new(bt, el, ed, p, funs)
end

function section(flines, header)

    nl = length(flines)
    for i in 1:nl
        if length(search(flines[i], header)) > 0
            return i
        end
    end

    return -1
end

function element(rea, idx)
    if length(search(uppercase(rea[1]), "QUAD")) > 0
        el = Mesh2d.Quad()
    else
        el = Mesh2d.Tri()
    end
    el.id = idx
    el.verts[:,1] = [parsefloat(x) for x = split(strip(rea[2]))][1:nverts(el)]
    el.verts[:,2] = [parsefloat(x) for x = split(strip(rea[3]))][1:nverts(el)]
    return el
end

function mesh(rea)

    isec = section(rea, "MESH DATA") + 1
    nelem = parseint(split(strip(rea[isec]), " ")[1])
    elems = Array(Mesh2d.Element2d, nelem)
    isec += 1
    for e = 1:nelem
        elems[e] = element(rea[isec:(isec+2)], e)
        isec += 3
    end

    return elems

end

function bndry_sec(rea, isec, nterms=2)

    s = split(strip(rea[isec]))
    tag = strip(s[1])[1]
    nums = [parsefloat(strip(ss)) for ss in s[2:end]]
    el = convert(Int, round(nums[1]))
    ed = convert(Int, round(nums[2]))
    p = nums[3:end]
    if islower(tag)
        funs = rea[(isec+1):(isec+nterms)]
        isec += nterms + 1
    else
        funs = Array(String, 0)
        isec += 1
    end
    return NektarBndry(tag, el, ed, p, funs), isec
end

function bndry_conds(rea, elems)
    isec = section(rea, "BOUNDARY CONDITIONS")+2
    bndrylst = Array(Mesh2d.Bndry, 0)
    for el in elems
        ne = Mesh2d.nedges(el)
        for edge = 1:ne
            b, isec = bndry_sec(rea, isec)
            if b.tag == 'E' || b.tag == 'P'
                el.neigh[edge,1] = convert(Int, round(b.params[1]))
                el.neigh[edge,2] = convert(Int, round(b.params[2]))
            else
                push!(bndrylst, b)
                el.hasbc = true
            end
        end
        if ne == 3
            isec += 1
        end
    end
    nb = length(bndrylst)
    for i = 1:nb
        el = bndrylst[i].elem
        ed = bndrylst[i].edge
        elems[el].neigh[ed,2] = -i
    end
    return bndrylst
end

type NekCurve <: Mesh2d.Curve2d
    cname::String
    line::String
end

function curves(rea, elems)

    isec = section(rea, "CURVED SIDE DATA") + 1
    num = convert(Int, round(parsefloat(split(strip(rea[isec]))[1])))

    crv = Array(Mesh2d.Curve2d, num)
    if num == 0
        return crv
    end
    crvdict = Dict{String,Int}()
    isec += 1
    for i = 1:num
        ctype = lowercase(strip(rea[isec]))
        isec += 1
        if ctype == "circle"
            s = split(strip(rea[isec]))
            xc = parsefloat(s[1])
            yc = parsefloat(s[2])
            rad = parsefloat(s[3])
            inside = true
            if rad < 0
                rad = -rad
                inside = false
            end
            tag = strip(s[4])
            crvdict[tag] = i
            crv[i] = Mesh2d.Circle(xc, yc, rad, inside)
        elseif ctype=="str"
            crv[i] = Segment()
            tag = strip(rea[isec])
            crvdict[tag] = i
        elseif ctype=="fil"
            crv[i] = NekCurve(ctype, rea[isec])
            tag = strip(split(strip(rea[isec]))[2])
            crvdict[tag] = i
        else
            crv[i] = NekCurve(ctype, rea[isec])
        end
    end
    isec += 1
    num = convert(Int, round(parsefloat(split(strip(rea[isec]))[1])))
    isec += 1
    for i = 1:num
        s = split(strip(rea[isec]))
        iside = parseint(strip(s[1]))
        iel = parseint(strip(s[2]))
        tag = strip(s[3])
        elems[iel].curve[iside] = crvdict[tag]
        elems[iel].curved = true
        isec += 1
    end

    return crv

end

function nektar(filename)
    fd = open(filename, "r")
    rea = readlines(fd)
    close(fd)
    elems = mesh(rea)
    crv = curves(rea, elems)
    bcs = bndry_conds(rea, elems)

    return Mesh2d.Mesh(elems, crv, bcs, rea)
end

end


"""
    pop_index(x)

Returns the index `x` that corresponds to `pop`. New subtypes of `AbstractIndex`
must implement a unique `pop_index` method.
"""
pop_index(x::SimpleIndex{name}) where {name} = SimpleIndex{name}(pop(values(x)))
pop_index(x::Index{name}) where {name} = Index{name}(pop(keys(x)), pop(values(x)))

"""
    popfirst_index(x)

Returns the index `x` that corresponds to `pop`. New subtypes of `AbstractIndex`
must implement a unique `popfirst_index` method.
"""
popfirst_index(x::SimpleIndex) = pop_index(x)
popfirst_index(x::Index{name}) where {name} = Index{name}(popfirst(keys(x)), pop(values(x)))

"""
    pop_index!(x)

Returns the index `x` that corresponds to `pop!`.
"""
function pop_index!(x::SimpleIndex)
    pop!(values(x))
    return x
end
function pop_index!(x::AbstractIndex)
    pop!(keys(x))
    pop!(values(x))
    return x
end

"""
    popfirst_index!(x)

Returns the index `x` that corresponds to `popfirst!`.
"""
popfirst_index!(x::SimpleIndex) = pop_index!(x)
function popfirst_index!(x::Index)
    popfirst!(keys(x))
    pop!(values(x))
    return x
end

"""
    push_index!(x, items...)

Returns the index `x` appropriate for `push(vector, items...)`. If `items` are
`Pairs` then the first element of each pair is treated as a key to be appended to
those of of `x`.
"""
function push_index!(x::SimpleIndex, items...)
    set_length!(values(x), length(x) + length(items))
    return x
end
function push_index!(x::AbstractIndex, items...)
    len = length(x) + length(items)
    set_length!(keys(x), len)
    set_length!(values(x), len)
    return x
end
function push_index!(x::AbstractIndex, items::Pair...)
    len = length(x) + length(items)
    set_length!(values(x), len)
    ks = keys(x)
    f = unique_offset(ks)
    for i in items
        k, v = i
        if k in ks
            push!(ks, add_offset(ks, k, f))
        else
            push!(ks, k)
        end
    end
    return x
end

"""
    pushfirst_index!(x, items...)
"""
pushfirst_index!(x::SimpleIndex, items...) = push_index!(x, items...)
pushfirst_index!(x::AbstractIndex, items...) = push_index!(x, items...)
function pushfirst_index!(x::AbstractIndex, items::Pair...)
    len = length(x) + length(items)
    set_length!(values(x), len)
    ks = keys(x)
    f = unique_offset(ks)
    for i in items
        k, v = i
        if k in ks
            pushfirst!(ks, add_offset(ks, k, f))
        else
            pushfirst!(ks, k)
        end
    end
    return x
end


using ITensorBenchmarks

# Run all of the benchmarks with a single thread and write the results
# By default, a verbose output is written to a log file
runbenchmarks(write_results = true)


export ishash

function ishash(str::AbstractString, algorithm::AbstractString)::Bool
    lengths = Dict([
        ("md5", 32)
        ("md4", 32)
        ("sha1", 40)
        ("sha256", 64)
        ("sha384", 96)
        ("sha512", 128)
        ("ripemd128", 32)
        ("ripemd160", 40)
        ("tiger128", 32)
        ("tiger160", 40)
        ("tiger192", 48)
        ("crc32", 8)
        ("crc32b", 8)
    ])

    hashReg = Regex("^[a-fA-F0-9]{$(lengths[algorithm])}\$")

    return contains(str, hashReg)
end


module NilNumber

using NNlib
import Random

"""
    nil = Nil()

`Nil` is a singleton type with a single instance `nil`.
Unlike `Nothing` and `Missing` it is a number: `Nil <: Real <: Number`.
"""
struct Nil <: Real end

@doc @doc(Nil)
const nil = Nil()

Nil(::T) where T<:Number = nil
(::Type{T})(::Nil) where T<:Number = nil
Base.convert(::Type{Nil}, ::Number) = nil

Base.float(::Type{Nil}) = Nil

for f in [:copy, :zero, :one, :oneunit,
          :+, :-, :abs, :abs2, :inv,
          :exp, :log, :log1p, :log2, :log10,
          :sqrt, :tanh, :conj]
  @eval Base.$f(::Nil) = nil
end

for f in [:+, :-, :*, :/, :^, :mod, :div, :rem]
  @eval Base.$f(::Nil, ::Nil) = nil
end

Base.:<(::Nil, ::Nil) = true
Base.:<=(::Nil, ::Nil) = true

Base.isnan(::Nil) = false
Base.isfinite(::Nil) = true
Base.typemin(::Type{Nil}) = nil
Base.typemax(::Type{Nil}) = nil

Base.promote_rule(x::Type{Nil}, y::Type{<:Number}) = Nil

Random.rand(rng::Random.AbstractRNG, ::Random.SamplerType{Nil}) = nil

end  # module

using .NilNumber: Nil, nil

"""
    outputsize(m, inputsize::Tuple; padbatch=false)

Calculate the size of the output from model `m`, given the size of the input.
Obeys `outputsize(m, size(x)) == size(m(x))` for valid input `x`.

Keyword `padbatch=true` is equivalent to using `(inputsize..., 1)`, and
returns the final size including this extra batch dimension.

This should be faster than calling `size(m(x))`. It uses a trivial number type,
which should work out of the box for custom layers.

If `m` is a `Tuple` or `Vector`, its elements are applied in sequence, like `Chain(m...)`.

# Examples
```julia-repl
julia> using Flux: outputsize

julia> outputsize(Dense(10, 4), (10,); padbatch=true)
(4, 1)

julia> m = Chain(Conv((3, 3), 3 => 16), Conv((3, 3), 16 => 32));

julia> m(randn(Float32, 10, 10, 3, 64)) |> size
(6, 6, 32, 64)

julia> outputsize(m, (10, 10, 3); padbatch=true)
(6, 6, 32, 1)

julia> outputsize(m, (10, 10, 3, 64))
(6, 6, 32, 64)

julia> try outputsize(m, (10, 10, 7, 64)) catch e println(e) end
┌ Error: layer Conv((3, 3), 3=>16), index 1 in Chain, gave an error with input of size (10, 10, 7, 64)
└ @ Flux ~/.julia/dev/Flux/src/outputsize.jl:114
DimensionMismatch("Input channels must match! (7 vs. 3)")

julia> outputsize([Dense(10, 4), Dense(4, 2)], (10, 1)) # Vector of layers becomes a Chain
(2, 1)
```
"""
function outputsize(m, inputsizes::Tuple...; padbatch=false)
  x = nil_input(padbatch, inputsizes...)
  return size(m(x))
end

nil_input(pad::Bool, s::Tuple{Vararg{Integer}}) = pad ? fill(nil, (s...,1)) : fill(nil, s)
nil_input(pad::Bool, multi::Tuple{Vararg{Integer}}...) = nil_input.(pad, multi)
nil_input(pad::Bool, tup::Tuple{Vararg{Tuple}}) = nil_input(pad, tup...)

function outputsize(m::Chain, inputsizes::Tuple{Vararg{Integer}}...; padbatch=false)
  x = nil_input(padbatch, inputsizes...)
  for (i,lay) in enumerate(m.layers)
    try
      x = lay(x)
    catch err
      str = x isa AbstractArray ? "with input of size $(size(x))" : ""
      @error "layer $lay, index $i in Chain, gave an error $str"
      rethrow(err)
    end
  end
  return size(x)
end

"""
    outputsize(m, x_size, y_size, ...; padbatch=false)

For model or layer `m` accepting multiple arrays as input,
this returns `size(m((x, y, ...)))` given `size_x = size(x)`, etc.

# Examples
```jldoctest
julia> x, y = rand(Float32, 5, 64), rand(Float32, 7, 64);

julia> par = Parallel(vcat, Dense(5, 9), Dense(7, 11));

julia> Flux.outputsize(par, (5, 64), (7, 64))
(20, 64)

julia> m = Chain(par, Dense(20, 13), softmax);

julia> Flux.outputsize(m, (5,), (7,); padbatch=true)
(13, 1)

julia> par(x, y) == par((x, y)) == Chain(par, identity)((x, y))
true
```
Notice that `Chain` only accepts multiple arrays as a tuple,
while `Parallel` also accepts them as multiple arguments;
`outputsize` always supplies the tuple.
"""
outputsize

## make tuples and vectors be like Chains

outputsize(m::Tuple, input::Tuple...; padbatch=false) = outputsize(Chain(m...), input...; padbatch=padbatch)
outputsize(m::AbstractVector, input::Tuple...; padbatch=false) = outputsize(Chain(m...), input...; padbatch=padbatch)

## bypass statistics in normalization layers

for layer in (:LayerNorm, :BatchNorm, :InstanceNorm, :GroupNorm)
  @eval (l::$layer)(x::AbstractArray{Nil}) = x
end

## fixes for layers that don't work out of the box

for (fn, Dims) in ((:conv, DenseConvDims), (:depthwiseconv, DepthwiseConvDims))
  @eval begin
    function NNlib.$fn(a::AbstractArray{Nil}, b::AbstractArray{Nil}, dims::$Dims)
      fill(nil, NNlib.output_size(dims)..., NNlib.channels_out(dims), size(a)[end])
    end

    function NNlib.$fn(a::AbstractArray{<:Real}, b::AbstractArray{Nil}, dims::$Dims)
      NNlib.$fn(fill(nil, size(a)), b, dims)
    end

    function NNlib.$fn(a::AbstractArray{Nil}, b::AbstractArray{<:Real}, dims::$Dims)
      NNlib.$fn(a, fill(nil, size(b)), dims)
    end
  end
end


abstract type HilbertAlgorithm{T} end

index_type(::HilbertAlgorithm{T}) where {T} = T

"""
    encode_hilbert(ha::HilbertAlgorithm{T}, X::Vector{A})

A 1-based Hilbert encoding. Both the Hilbert index and the axes start counting
at 1 instead of 0.
"""
function encode_hilbert(gg::HilbertAlgorithm{T}, X::Vector{A}) where {A, T}
    encode_hilbert_zero(gg, X .- one(A)) + one(T)
end

"""
    decode_hilbert!(ha::HilbertAlgorithm{T}, X::Vector{A})

A 1-based Hilbert decode, from [`decode_hilbert_zero!`](@ref). Both the Hilbert
index and the axes start counting at 1 instead of 0.
"""
function decode_hilbert!(gg::HilbertAlgorithm{T}, X::Vector{A}, h::T) where {A,T}
    decode_hilbert_zero!(gg, X, h - one(T))
    X .+= one(A)
end


abstract AbstractNoiseModel

noise(s::Symbol,args...) = _noise(Val{s},args...)
noisemodelname(::AbstractNoiseModel) = "AbstractNoiseModel"

function show(io::IO,n::AbstractNoiseModel)
  println(io,noisemodelname(n))
  for f in fieldnames(n)
    print(io," ",f,": ",typeof(getfield(n,f)))
    println(io)
  end
  nothing
end


using ImageCore, Colors, FixedPointNumbers, OffsetArrays
using Base.Test

@testset "reinterpret" begin
    a = rand(Gray{U8}, (4,5))
    for T in (Gray{U8}, Gray{Float32}, Gray{Float64})
        b = @inferred(convert(Array{T}, a))
        rb = @inferred(reinterpret(eltype(T), b))
        if ImageCore.squeeze1
            @test isa(rb, Array{eltype(T),2})
            @test size(rb) == (4,5)
        else
            @test isa(rb, Array{eltype(T),3})
            @test size(rb) == (1,4,5)
        end
        c = copy(rb)
        rc = @inferred(reinterpret(T, c))
        @test isa(rc, Array{T,2})
        @test size(rc) == (4,5)
    end
    a = rand(AGray{U8}, (4,5))
    for T in (AGray{U8}, GrayA{Float32}, AGray{Float64})
        b = @inferred(convert(Array{T}, a))
        rb = @inferred(reinterpret(eltype(T), b))
        @test isa(rb, Array{eltype(T),3})
        @test size(rb) == (2,4,5)
        c = copy(rb)
        rc = @inferred(reinterpret(T, c))
        @test isa(rc, Array{T,2})
        @test size(rc) == (4,5)
    end
    a = rand(RGB{U8}, (4,5))
    for T in (RGB{U8}, HSV{Float32}, XYZ{Float64})
        b = @inferred(convert(Array{T}, a))
        rb = @inferred(reinterpret(eltype(T), b))
        @test isa(rb, Array{eltype(T),3})
        @test size(rb) == (3,4,5)
        c = copy(rb)
        rc = @inferred(reinterpret(T, c))
        @test isa(rc, Array{T,2})
        @test size(rc) == (4,5)
    end
    for a in (rand(RGB{U8}, 4), rand(RGB{U8}, (4,5)))
        b = @inferred(reinterpret(HSV{Float32}, float32.(a)))
        @test isa(b, Array{HSV{Float32}})
        @test ndims(b) == ndims(a)
    end
    a = rand(ARGB{U8}, (4,5))
    for T in (ARGB{U8}, AHSV{Float32}, AXYZ{Float64})
        b = @inferred(convert(Array{T}, a))
        rb = @inferred(reinterpret(eltype(T), b))
        @test isa(rb, Array{eltype(T),3})
        @test size(rb) == (4,4,5)
        c = copy(rb)
        rc = @inferred(reinterpret(T, c))
        @test isa(rc, Array{T,2})
        @test size(rc) == (4,5)
    end
    # RGB1/RGB4
    a = rand(RGB{U8}, (4,5))
    for T in (RGB1{U8},RGB4{Float32})
        b = @inferred(convert(Array{T}, a))
        rb = @inferred(reinterpret(eltype(T), b))
        @test isa(rb, Array{eltype(T),3})
        @test size(rb) == (4,4,5)
        c = copy(rb)
        rc = @inferred(reinterpret(T, c))
        @test isa(rc, Array{T,2})
        @test size(rc) == (4,5)
    end
    a = [RGB(1,0,0) RGB(0,0,1);
         RGB(0,1,0) RGB(1,1,1)]
    @test reinterpret(U8, a) == cat(3, [1 0; 0 1; 0 0], [0 1; 0 1; 1 1])
    b = convert(Array{BGR{U8}}, a)
    @test reinterpret(U8, b) == cat(3, [0 0; 0 1; 1 0], [1 1; 0 1; 0 1])
    # indeterminate type tests
    a = Array(RGB{AbstractFloat},3)
    @test_throws ErrorException reinterpret(Float64, a)
    Tu = TypeVar(:T)
    a = Array(RGB{Tu},3)
    @test_throws ErrorException reinterpret(Float64, a)
end

@testset "convert" begin
    a = [RGB(1,0,0) RGB(0,0,1);
         RGB(0,1,0) RGB(1,1,1)]
    c = @inferred(convert(Array{BGR}, a))
    @test eltype(c) == BGR{U8}
    c = @inferred(convert(Array{BGR{Float32}}, a))
    @test eltype(c) == BGR{Float32}
    c = @inferred(convert(Array{Lab}, a))
    @test eltype(c) == Lab{Float32}
end

@testset "eltype conversion" begin
    @test float32(Float64) == Float32
    @test float32(U8)      == Float32
    @test float64(RGB{U8}) == RGB{Float64}

    a = [RGB(1,0,0) RGB(0,0,1);
         RGB(0,1,0) RGB(1,1,1)]
    @test eltype(a) == RGB{U8}
    @test eltype(u8.(a))       == RGB{U8}
    @test eltype(ufixed8.(a))  == RGB{U8}
    @test eltype(ufixed10.(a)) == RGB{UFixed10}
    @test eltype(ufixed12.(a)) == RGB{UFixed12}
    @test eltype(ufixed14.(a)) == RGB{UFixed14}
    @test eltype(ufixed16.(a)) == RGB{U16}
    @test eltype(u16.(a))      == RGB{U16}
#    @test eltype(float16.(a)) == RGB{Float16}
    @test eltype(float32.(a)) == RGB{Float32}
    @test eltype(float64.(a)) == RGB{Float64}

    a = U8[0.1,0.2,0.3]
    @test eltype(a) == U8
    @test eltype(u8.(a))       == U8
    @test eltype(ufixed8.(a))  == U8
    @test eltype(ufixed10.(a)) == UFixed10
    @test eltype(ufixed12.(a)) == UFixed12
    @test eltype(ufixed14.(a)) == UFixed14
    @test eltype(ufixed16.(a)) == U16
    @test eltype(u16.(a))      == U16
#    @test eltype(float16.(a)) == Float16
    @test eltype(float32.(a)) == Float32
    @test eltype(float64.(a)) == Float64

    a = OffsetArray(U8[0.1,0.2,0.3], -1:1)
    @test eltype(a) == U8
    @test eltype(u8.(a))       == U8
    @test eltype(ufixed8.(a))  == U8
    @test eltype(ufixed10.(a)) == UFixed10
    @test eltype(ufixed12.(a)) == UFixed12
    @test eltype(ufixed14.(a)) == UFixed14
    @test eltype(ufixed16.(a)) == U16
    @test eltype(u16.(a))      == U16
#    @test eltype(float16.(a)) == Float16
    @test eltype(float32.(a)) == Float32
    @test eltype(float64.(a)) == Float64
    @test indices(float32.(a)) == (-1:1,)
end

nothing


### Functions for Pose Graph Input/Output ###
### Ted Steiner ###
### September 2014 ###

# Note: See file_format.txt for text file information.

##############
### Output ###
##############

# Write BetweenFactors to text file as EDGE2's
function writeMeasurements( filename::String, meas::Union(Vector{BetweenFactor},Array{Factor,1}) )
    f = open(filename,"w")
    writeMeasurements(f,meas)
    close(f)
    nothing
end

# Write out truth data and trajectory information
function writeTruthFile( filename::String,
                         poses::Array{Pose2D,1};
                         waypoints::Array{Int,1}=Int[],
                         anchors::Array{Array{Int,1},1}=Array{Int,1}[],
                         flags::Dict{String,Any}=Dict{String,Any}() )
    f = open(filename,"w")

    writeFlags(f,flags)
    write(f,@sprintf("\n"))

    writeWaypoints(f,waypoints)
    write(f,@sprintf("\n"))

    if length(anchors) > 0
        writeAnchors(f,anchors)
        write(f,@sprintf("\n"))
    end
    write(f,@sprintf("\n"))
    write(f,@sprintf("\n"))

    writePoses(f,poses)
    write(f,@sprintf("\n"))

    close(f)
    return nothing
end

function writeMeasurements( f::IOStream, meas::Union(Vector{BetweenFactor},Vector{Factor}) )
    for m in meas
        write(f,factor2string(m))
    end

    return nothing
end

function factor2string( m::BetweenFactor )
    @sprintf("EDGE2 %d %d %f %f %f %f 0.0 0.0 %f 0.0 %f\n",m.s,m.t,m.meas.dx,m.meas.dy,m.meas.dT,m.info.ff,m.info.ss,m.info.rr)
end

function factor2string( m::PriorFactor )
    @sprintf("PRIOR2 %d %f %f %f %f 0.0 0.0 %f 0.0 %f\n",m.pose,m.meas.dx,m.meas.dy,m.meas.dT,m.info.ff,m.info.ss,m.info.rr)
end

# Write poses to text file as Pose2d_Node's
function writePoses( f::IOStream, poses::Array{Pose2D,1} )
    count = 0
    for p in poses
        str = @sprintf("Pose2d_Node %d (%f, %f, %f)\n",count,p.x,p.y,p.theta)
        write(f,str)
        count += 1
    end

    return nothing
end

function writeAnchors( f::IOStream, anchors::Array{Int,1} )
    str = @sprintf("ANCHOR %d [%s]\n",length(anchors),vectorToString(anchors))
    write(f,str)
    return nothing
end

function writeAnchors( f::IOStream, anchors::Array{Array{Int,1},1} )
    for a in anchors
        str = @sprintf("ANCHOR %d [%s]\n",length(a),vectorToString(a))
        write(f,str)
    end
    return nothing
end

function writeWaypoints( f::IOStream, waypoints::Array{Int,1} )
    str = @sprintf("WAYPOINTS %d [%s]\n",length(waypoints),vectorToString(waypoints))
    write(f,str)
    return nothing
end

function writeFlags( f::IOStream, flags::Dict{String,Any} )
    for (name,value) in flags
        write(f,"FLAG $(uppercase(name)) $value\n")
    end
    return nothing
end

function vectorToString( v::Array{Int,1} )
    v_str = String[]
    for k = 1:length(v)
        push!(v_str,string(v[k]))
    end

    return join(v_str," ")
end

#############
### Input ###
#############

# Read pose locations from iSAM output text file
function readiSAMOutput( filename::String )
    return open(parseISAMOutput, filename)
end

function readiSAMTruth( filename::String )
    return open(parseISAMTruth, filename)
end

function readiSAMOutputCov( filename::String )
    return open(parseISAMOutputCov, filename)
end

function parseISAMOutput( f::IOStream )
    poses = Pose2D[]
    delim = Char[' ';',';'(';')']

    for line in eachline(f)
        if beginswith(line,"Pose2d_Node")
            s = split(line,delim)
            x = float(s[4])
            y = float(s[6])
            theta = float(s[8])
            push!(poses,Pose2D(x,y,theta))
        end
    end

    return poses
end

function parseISAMOutputCov( f::IOStream )
    cov_all = readdlm(f,' ',String)
    cov_s = cov_all[:,[3,6,8]]
    cov = float(cov_s)

    return cov
end

function parseISAMTruth( f::IOStream )
    # Helper function
    checkint(x) =
        try
            int(string(x))
            return true
        catch
            return false
        end

    truth = Dict{String,Any}()
    anchor_sets = Array{Int,1}[]
    delim = Char[' ';',';'(';')';'[';']';'\n']

    for line in eachline(f)
        s = split(line,delim)
        if beginswith(line,"ANCHOR")
            anchors = Int[]
            for k = 3:length(s)
                if checkint(s[k])
                    push!(anchors,int(s[k]))
                end
            end
            push!(anchor_sets,anchors)
        elseif beginswith(line,"WAYPOINTS")
            waypoints = Int[]
            for k = 3:length(s)
                if checkint(s[k])
                    push!(waypoints,int(s[k]))
                end
            end
            truth[s[1]] = waypoints
        elseif beginswith(line,"FLAG")
            s = split(line,delim)
            if checkint(s[3])
                truth[s[2]] = int(s[3])
            else
                truth[s[2]] = s[3]
            end
        end
    end

    truth["poses"] = parseISAMOutput(f)

    if length(anchor_sets) > 0
        truth["anchor_sets"] = anchor_sets
    end

    return truth
end


# This file contains the definition for the Renewables struct.

function get_res_dict(renewables_df::DataFrame)

   renewables_stacked = stack(renewables_df, Not(:node), variable_name=:fuel,
			           value_name=:capacity)

	renewables_stacked[!, :unit] = Symbol.(string.(renewables_stacked[!, :fuel]) .* "_"
		                      .* string.(renewables_stacked[!, :node]))

	renewables_stacked = renewables_stacked[:, [:unit, :node, :fuel, :capacity]]
	renewables_stacked = renewables_stacked[renewables_stacked[!, :capacity] .> 0, :]
	renewables_dict = df_to_dict_with_id(renewables_stacked)

	return renewables_stacked[!, :unit], renewables_dict
end

function get_avail_dict(renewables_avail_dict::Dict, renewables_dict)

	fuel = renewables_dict[:fuel]
	node = renewables_dict[:node]

	avail_dict = Dict{Symbol,Array{Float64, 1}}()
	for unit in keys(fuel)

		f = fuel[unit]
		n = Symbol(node[unit])

		if haskey(renewables_avail_dict, f)
			if Symbol(node[unit]) in names(renewables_avail_dict[f])
				avail_dict[unit] = renewables_avail_dict[f][!, n]
			else
				avail_dict[unit] = fill(0.0, 8760)
			end
		else
		 	avail_dict[unit] = renewables_avail_dict[:other][!, f]
		end
	end

	return avail_dict
end

function calc_infeed(cap_dict, avail_dict)

	infeed = map(collect(keys(cap_dict))) do unit
		cap = cap_dict[unit]
		avail = avail_dict[unit]

		unit => avail * cap
	end |> Dict

	return infeed
end

mutable struct Renewables
	unit
	fuel
	node
	capacity
	infeed

	function Renewables(renewables_df::DataFrame, renewables_avail_dict::Dict)

		units, renewables_dict = get_res_dict(renewables_df)
		avail_dict = get_avail_dict(renewables_avail_dict, renewables_dict)
		infeed = calc_infeed(renewables_dict[:capacity], avail_dict)

		return new(units, renewables_dict[:fuel], renewables_dict[:node], renewables_dict[:capacity], infeed)
	end
end


using MLJBase
using PersistenceDiagrams
using Suppressor
using Tables
using Test

diagrams = (
    dim_0=[
        PersistenceDiagram([(0.0, Inf), (0.0, 1.0)]),
        PersistenceDiagram([(0.0, Inf), (0.0, 1.5)]),
    ],
    dim_1=[
        PersistenceDiagram([(0.0, 1.0), (1.2, 2.0)]),
        PersistenceDiagram([(1.0, 1.1), (1.3, 1.9)]),
    ],
)
table = MLJBase.table(diagrams)

n_rows(x) = length(Tables.rows(x))
n_cols(x) = length(Tables.columnnames(x))

"""
    test_clean(model_type, kwargs; expect_warning=true)

Tests that for given model and kwargs:

* cleaning shows warning if `expect_warning=true`.
* cleaning the second time does not change anything and does not give warnings.
"""
function test_clean(model_type, kwargs; expect_warning=true)
    @testset "`clean!` with $kwargs" begin
        # Does clean warn?
        local model
        warning = @capture_err begin
            model = model_type(; kwargs...)
        end
        if expect_warning
            @test warning ≠ ""
        else
            @test warning == ""
        end

        # Cleaning again should not warn and should not change anything.
        prev_model = deepcopy(model)
        warning = clean!(model)
        @test warning == ""
        for field in fieldnames(model_type)
            @test getfield(model, field) == getfield(prev_model, field)
        end
    end
end

@testset "PersistenceImageVectorizer" begin
    for kwargs in (NamedTuple(), (sigma=0.1, slope_end=0.5, height=7), (sigma=0.1, width=6))
        model = PersistenceImageVectorizer(; kwargs...)
        mach = machine(model, table)
        fit!(mach; verbosity=0)
        res = transform(mach, table)

        @test n_rows(res) == 2
        @test n_cols(res) == model.width * model.height + model.height
    end

    for faulty_kwargs in (
        (; width=0),
        (; height=0),
        (; distribution="a"),
        (; weight=1),
        (; sigma=0),
        (; slope_end=2),
        (; margin=-1),
        (; sigma=1, distribution=(+)),
        (; weight=(_, _) -> 0, slope_end=0.5),
        (; distribution=(_, _) -> 0, sigma=1),
    )
        test_clean(PersistenceImageVectorizer, faulty_kwargs; expect_warning=true)
    end
    for good_kwargs in (
        NamedTuple(),
        (; width=5, height=10),
        (; sigma=1, slope_end=0.2),
        (; distribution=(_, _) -> 0, slope_end=0.001),
        (; weight=(_, _) -> 0, sigma=0.9, width=13),
        (; zero_start=true),
        (; margin=0),
    )
        test_clean(PersistenceImageVectorizer, good_kwargs; expect_warning=false)
    end
end

@testset "PersistenceCurveVectorizer" begin
    for kwargs in (
        NamedTuple(),
        (length=15, normalize=true),
        (integrate=false, curve=:midlife),
        (length=3, curve=:midlife_entropy),
    )
        model = PersistenceCurveVectorizer(; kwargs...)
        mach = machine(model, table)
        fit!(mach; verbosity=0)
        res = transform(mach, table)

        @test n_rows(res) == 2
        @test n_cols(res) == model.length * 2
    end
    birth_fun(i, _, _) = birth(i)
    for faulty_kwargs in (
        (; curve=:betti, fun=birth_fun),
        (; curve=:life, stat=mean),
        (; curve=:midlife, fun=birth_fun, stat=mean),
        (; curve=:life_entropy, fun=birth_fun),
        (; curve=:midlife_entropy, stat=mean),
        (; curve=:pd_thresholding, normalize=true),
        (; curve=:silhuette, normalize=true),
        (; curve=:something),
        (; length=0),
        (; length=-1),
    )
        test_clean(PersistenceCurveVectorizer, faulty_kwargs; expect_warning=true)
    end
    for good_kwargs in (
        NamedTuple(),
        (; curve=:custom),
        (; curve=:betti, length=100),
        (; curve=:life),
        (; curve=:midlife, normalize=false),
        (; curve=:life_entropy, normalize=true),
        (; curve=:midlife_entropy, integrate=false),
        (; curve=:pd_thresholding, integrate=true),
        (; curve=:silhuette, length=10),
        (; fun=birth_fun),
        (; stat=mean),
        (; fun=birth_fun, stat=mean, curve=:custom),
    )
        test_clean(PersistenceCurveVectorizer, good_kwargs; expect_warning=false)
    end
end

@testset "PersistenceLandscapeVectorizer" begin
    for kwargs in (NamedTuple(), (length=15, n_landscapes=5), (; n_landscapes=3))
        model = PersistenceLandscapeVectorizer(; kwargs...)
        mach = machine(model, table)
        fit!(mach; verbosity=0)
        res = transform(mach, table)

        @test n_rows(res) == 2
        @test n_cols(res) == model.length * model.n_landscapes * 2
    end
    for faulty_kwargs in ((; length=-1), (; n_landscapes=0), (; length=0, n_landscapes=-1))
        test_clean(PersistenceLandscapeVectorizer, faulty_kwargs; expect_warning=true)
    end
    for good_kwargs in
        (NamedTuple(), (; length=100), (; n_landscapes=4), (; n_landscapes=3, length=15))
        test_clean(PersistenceLandscapeVectorizer, good_kwargs; expect_warning=false)
    end
end


using TextAnalysis

struct StemmedToken
    text::String
    stemmed::Vector{String}
end

function unhyphenAndStem(stemmer::Stemmer, toks::Vector{String})::Vector{StemmedToken}
    result = Vector{StemmedToken}()
    for tok in toks
        if tok == ""
            continue
        end
        tok = replace(tok, "/" => "-or-")
        while startswith(tok, "-")
            tok = tok[2:end]
        end
        while endswith(tok, "-")
            tok = tok[1:end - 1]
        end
        subToks = split(tok, "-")
        stemmedToken = StemmedToken(tok, stem(stemmer, subToks))
        push!(result, stemmedToken)
    end
    result
end

IDPair = Tuple{Int32,Int32}
IDPostfixes = Set{Vector{Int32}}

function getVocab(tokGroups::Vector{Vector{StemmedToken}})::Tuple{Dict{String,Int32},Dict{IDPair,IDPostfixes},Vector{Vector{Int32}}}
    vocab = Dict{String,Int32}()
    strongLinkForest = Dict{IDPair,IDPostfixes}()
    numGroups = length(tokGroups)
    tokIDGroups = Vector{Vector{Int32}}(undef, numGroups)
    for (groupID, tokGroup) in enumerate(tokGroups)
        tokIDGroup = Vector{Int32}()
        for stemmedToken in tokGroup
            for subtok in stemmedToken.stemmed
                if haskey(vocab, subtok)
                    push!(tokIDGroup, vocab[subtok])
                    continue
                end
                subtokID = length(vocab) + 1
                vocab[subtok] = subtokID
                push!(tokIDGroup, subtokID)
            end
            numSubToks = length(stemmedToken.stemmed)
            if numSubToks > 1
                idPair = (tokIDGroup[end - numSubToks + 1], tokIDGroup[end - numSubToks + 2])
                postfix = tokIDGroup[end - numSubToks + 3:end]
                if !haskey(strongLinkForest, idPair)
                    strongLinkForest[idPair] = IDPostfixes()
                end
                push!(strongLinkForest[idPair], postfix)
            end
        end
        tokIDGroups[groupID] = tokIDGroup
    end
    vocab, strongLinkForest, tokIDGroups
end

const stopWords = Set{String}([
    "a",
	"an",
	"and",
	"as",
	"based",
	"by",
	"for",
	"from",
	"in",
	"on",
	"of",
    "over",
	"that",
	"the",
	"this",
	"to",
	"via",
	"with",
	"without",
	"is",
	"are",
	"be",
	"you",
	"we",
	"they",
	"it",
	"your",
	"our",
	"their",
	"its",
	"what",
	"when",
	"where",
	"how",
	"do",
	"use",
	"here",
	"there",
	"using",
    "than",
	"、",
	",",
	"，",
	":",
	"：",
	".",
	"。",
	"‧",
	"!",
	"！",
	"?",
	"？",
	";",
	"；",
	"(",
	"（",
	")",
	"）",
	"'",
	"‘",
	"’",
	"\"",
	"「",
	"」",
	"“",
	"”",
	"`",
	"…",
    "</s>",
])

function getNonStopWords(wordVectors::WordVectors)::Tuple{Vector{String},Dict{String,Int}}
    nonStopWords = Vector{String}()
    nonStopWordIDs = Dict{String,Int}()
    for (wordID, word) in enumerate(wordVectors.vocab)
        if !(word in stopWords)
            push!(nonStopWords, word)
            nonStopWordIDs[word] = wordID
        end
    end
    (nonStopWords, nonStopWordIDs)
end

function getCardinalities(nonStopWords::Vector{String}, nonStopWordIDs::Dict{String,Int}, phraseIDGroups::Vector{Vector{Int}})::Vector{Int}
    numPhrases = length(nonStopWords)
    idMap = Dict{Int,Int}()
    for (newID, phrase) in enumerate(nonStopWords)
        idMap[nonStopWordIDs[phrase]] = newID
    end

    phraseFreqs = fill(0, numPhrases)
    for idGroup in phraseIDGroups
        for id in idGroup
            phraseFreqs[idMap[id]] += 1
        end
    end

    phraseFreqs
end

function normalizeVectors(wordVectors::WordVectors, nonStopWords::Vector{String})::Array{Float64,2}
    numDims, numWords = size(wordVectors)
    numVectors = length(nonStopWords)
    result = Array{Float64,2}(undef, (numDims, numVectors))
    for i in 1:numVectors
        vector = get_vector(wordVectors, nonStopWords[i])
        w = 1.0 / norm(vector)
        result[:,i] = vector * w
    end
    result
end

function getVectors(wordVectors::WordVectors, nonStopWords::Vector{String})::Array{Float64,2}
    numDims, numWords = size(wordVectors)
    numVectors = length(nonStopWords)
    result = Array{Float64,2}(undef, (numDims, numVectors))
    for i in 1:numVectors
        vector = get_vector(wordVectors, nonStopWords[i])
        result[:,i] = vector
    end
    result
end

function getPhrasePairFreqs(nonStopWords::Vector{String}, nonStopWordIDs::Dict{String,Int}, phraseIDGroups::Vector{Vector{Int}})::Dict{Tuple{Int,Int},Int}
    numPhrases = length(nonStopWords)
    idMap = Dict{Int,Int}()
    for (newID, phrase) in enumerate(nonStopWords)
        idMap[nonStopWordIDs[phrase]] = newID
    end

    pairFreqs = Dict{Tuple{Int,Int},Int}()
    for idGroup in phraseIDGroups
        numIDsInGroup = length(idGroup)
        convertedIDs = [idMap[id] for id in idGroup]
        for (j1, id1) in enumerate(convertedIDs)
            for j2 in j1 + 1:numIDsInGroup
                id2 = convertedIDs[j2]
                pair = if id1 < id2
                    (id1, id2)
                else
                    (id2, id1)
                end
                pairFreqs[pair] = get(pairFreqs, pair, 0) + 1
            end
        end
    end

    pairFreqs
end

function mergeAspects(aspects::Vector{Set{Int}}, connMat::Array{Float64,2})::Array{Float64,2}
    numAspects = length(aspects)
    newConnMat = Array{Float64,2}(undef, (numAspects, numAspects))
    for i in 1:numAspects
        for j in 1:numAspects
            if i != j
                conn = 0.0
                for ui in aspects[i]
                    for uj in aspects[j]
                        conn += connMat[ui,uj]
                    end
                end
                newConnMat[i,j] = conn
            else
                newConnMat[i,j] = 0.0
            end
        end
    end
    newConnMat
end


using SnoopCompile
invalidations = @snoopr using EAGO

@show length(invalidations)
trees = invalidation_trees(invalidations)
#methinvs = trees[end]
#root = methinvs.backedges[end]
#ascend(root)


using DataDeps

register(DataDep(
  "year-prediction-msd",
  "http://archive.ics.uci.edu/ml/datasets/YearPredictionMSD",
  "https://archive.ics.uci.edu/ml/machine-learning-databases/00203/YearPredictionMSD.txt.zip",
  "06f801af323bb7798e800583acce4ea1ed2697ac12c23f4424aea0a7a3d09e11",
  post_fetch_method=(path -> begin
    unpack(path)
    new_path = joinpath(dirname(path), "YearPredictionMSD.txt")
    mv(new_path, path)

    UCIData.process_dataset(path,
        target_index=1,
        feature_indices=2:91,
    )
  end),
))


using CompactBasisHelFEM
using Test

@testset "CompactBasisHelFEM.jl" begin
    # Write your tests here.
end


module testUtils

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

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

tm = TimeData.readTimedata("/home/chris/.julia/v0.3/TimeData/data/logRet.csv")

####################
## random weights ##
####################

## randInvestments
wgts = AssetMgmt.randInvestments(tm)
@test isa(wgts, AssetMgmt.Investments)
@test AssetMgmt.idx(wgts) == idx(tm)
@test AssetMgmt.names(wgts) == names(tm)

######################
## equally weighted ##
######################

## equWgtInvestments
wgts = AssetMgmt.equWgtInvestments(tm)
@test isa(wgts, AssetMgmt.Investments)
@test AssetMgmt.idx(wgts) == idx(tm)
@test AssetMgmt.names(wgts) == names(tm)

wgts1 = AssetMgmt.core(wgts)[1, :]
wgts2 = AssetMgmt.core(wgts)[2, :]
@test (wgts1 == wgts2)

end


module TreeView

using LightGraphs, TikzGraphs
using MacroTools
using CommonSubexpressions

export LabelledTree, walk_tree, walk_tree!, draw, @tree, @tree_with_call,
        tikz_representation

export make_dag, @dag, @dag_cse

abstract type LabelledDiGraph
end

struct LabelledTree <: LabelledDiGraph
    g::DiGraph
    labels::Vector{Any}
end

add_numbered_vertex!(g) = (add_vertex!(g); top = nv(g))  # returns the number of the new vertex

include("tree.jl")
include("dag.jl")
include("display.jl")

end # module


module Archimed

import XMLDict.xml_dict
import EzXML.readxml
import EzXML.root
import Colors.RGBA
import LinearAlgebra.I
import StaticArrays.SMatrix

using MeshIO
using GeometryTypes

# 3D:
export read_opf,extract_opf_ref_meshes,extract_opf_materials
export meshes_from_topology,merge_meshes
export material,extract_opf_shapes,meshes_color

# For debbug:
export attr_type
export mesh_from_topology!

include("3D_related/read_opf.jl")
include("3D_related/meshes.jl")
include("3D_related/materials.jl")
include("3D_related/shapes.jl")
include("3D_related/mesh_colors.jl")
include("3D_related/3D_types.jl")

end # module


function simulate!(rng::AbstractRNG,
                   sim::Simulation;
                   tmax::Float64=Inf,
                   nmax::Int64=100000,
                   pmax::Float64=Inf)
  if tmax <= 0.0
    @error "The simulation time maximum must be > 0.0"
  elseif nmax < 1
    @error "The iteration maximum must be > 0"
  elseif pmax <= 0.0
    @error "The processing time maximum must be > 0.0"
  else
    @debug "Simulating until: \n* a simulation time maximum of $tmax,\n* an iteration maximum of $nmax, or,\n* a processing time maximum of $pmax seconds.\n"
  end
  stoptime = time() + pmax
  while true
    # Generate the new event and associated specific transmission when applicable
    event = generate(rng, Event, sim.event_rates, sim.time)
    transmission = generate(rng, Transmission, sim.transmission_rates, event)
    if _time(event) > tmax
      sim.time = tmax
      @debug "Simulation stopped: simulation time maximum reached"
      break
    else
      update!(sim, event, transmission)
    end
    if sim.iterations >= nmax
      @debug "Simulation stopped: iteration maximum reached"
      break
    elseif pmax < Inf && time() >= stoptime
      @debug "Simulation stopped: processing time maximum reached"
      break
    end
  end
  return sim
end

simulate!(sim::Simulation; kwargs...) = simulate!(default_rng(), sim; kwargs...)


eye(args...)


const FWD_SUFFIX = "_fwd"
const BKWD_SUFFIX = "_bkwd"

function split_rxns(metnet::MetNet, iders;
        get_fwd_ider::Function = (rxn) -> string(rxn, FWD_SUFFIX),
        get_bkwd_ider::Function = (rxn) -> string(rxn, BKWD_SUFFIX),
        on_split::Function = (new_model, fwd_idx, bkwd_idx) -> nothing
    )

    M, N = size(metnet)
    idxs = rxnindex.([metnet], iders)
    newM, newN = M, N + length(idxs)

    new_model = expanded_model(metnet, newM, newN)

    for (i, fwd_idx) in enumerate(idxs)

        # backup
        orig_rxn = metnet.rxns[fwd_idx]
        orig_lb = metnet.lb[fwd_idx]

        # transform to forward reaction
        new_model.rxns[fwd_idx] = get_fwd_ider(orig_rxn)
        new_model.lb[fwd_idx] = 0.0

        # add backward reaction
        bkwd_idx = findempty(new_model, :rxns)
        new_model.rxns[bkwd_idx] = get_bkwd_ider(orig_rxn)
        new_model.S[:,bkwd_idx] .= -new_model.S[:,fwd_idx]
        new_model.c[bkwd_idx] = 0.0
        new_model.lb[bkwd_idx] = 0.0
        new_model.ub[bkwd_idx] = abs(orig_lb)

        on_split(new_model, fwd_idx, bkwd_idx)
    end

    return new_model

end

"""
    Returns a new MetNet with no reversible reactions.
"""
function split_revs(metnet::MetNet;
        get_fwd_ider::Function = (rxn) -> string(rxn, FWD_SUFFIX),
        get_bkwd_ider::Function = (rxn) -> string(rxn, BKWD_SUFFIX),
        on_rev::Function = (new_model, fwd_idx, bkwd_idx) -> nothing
    ) # TODO Add tests

    return split_rxns(metnet, revs(metnet);
        get_fwd_ider, get_bkwd_ider, on_split = on_rev
    )
end

# function split_revs(metnet::MetNet;
#         get_fwd_ider::Function = (rxn) -> string(rxn, FWD_SUFFIX),
#         get_bkwd_ider::Function = (rxn) -> string(rxn, BKWD_SUFFIX),
#         on_rev!::Function = (new_model, fwd_idx, bkwd_idx) -> nothing
#     ) # TODO Add tests

#     M, N = size(metnet)
#     revs = revs(metnet)
#     newM, newN = M, N + length(revs)

#     new_model = expanded_model(metnet, newM, newN)

#     # I only need to update the rev reactions
#     _check_and_push!(v::AbstractVector, idx, val) = checkbounds(Bool, v, idx) && push!(v, val)

#     for (i, fwd_idx) in enumerate(findall(revs))

#         # backup
#         orig_rxn = metnet.rxns[fwd_idx]
#         orig_lb = metnet.lb[fwd_idx]

#         # transform to forward reaction
#         new_model.rxns[fwd_idx] = get_fwd_ider(orig_rxn)
#         new_model.lb[fwd_idx] = 0.0

#         # add backward reaction
#         bkwd_idx = findempty(new_model, :rxns)
#         new_model.rxns[bkwd_idx] = get_bkwd_ider(orig_rxn)
#         new_model.S[:,bkwd_idx] .= -new_model.S[:,fwd_idx]
#         new_model.c[bkwd_idx] = 0.0
#         new_model.lb[bkwd_idx] = 0.0
#         new_model.ub[bkwd_idx] = abs(orig_lb)

#         on_rev!(new_model, fwd_idx, bkwd_idx)
#     end

#     return new_model
# end


#!/usr/local/bin/julia

using ArgParse

function countNucleotides(DNAstring::String)
	A = count(c->c=='A', DNAstring)
	C = count(c->c=='C', DNAstring)
	G = count(c->c=='G', DNAstring)
	T = count(c->c=='T', DNAstring)

	return A, C, G, T
end

function main(args)
	# Argument parsing
	configs = ArgParseSettings()

	add_arg_group!(configs, "Options:")
	@add_arg_table! configs begin
		"--input", "-i"
			help	 = "Input filename"
			required = true
			action	 = :store_arg
			arg_type = String
		"--output", "-o"
			help	 = "Output filename"
			required = true
			action	 = :store_arg
			arg_type = String
		end
	# Store arguments to variables
	parsed_args = parse_args(args, configs)
	infile      = parsed_args["input"]
	outfile     = parsed_args["output"]

	# Read file and count nucleotides
	DNAstring  = open(f->read(f, String), infile)
	@show A, C, G, T = countNucleotides(DNAstring)
	open(f->write(f, "$A $C $G $T"), outfile, "w+")

end

main(ARGS)


# convert from Julia expression into NodeData form

function expr_to_nodedata(
    ex::Expr,
    r::UserOperatorRegistry = UserOperatorRegistry(),
)
    nd = NodeData[]
    values = Float64[]
    expr_to_nodedata(ex, nd, values, -1, r)
    return nd, values
end

function expr_to_nodedata(
    ex::Expr,
    nd::Vector{NodeData},
    values::Vector{Float64},
    parentid,
    r::UserOperatorRegistry,
)
    myid = length(nd) + 1
    if isexpr(ex, :call)
        op = ex.args[1]
        if length(ex.args) == 2
            id =
                haskey(univariate_operator_to_id, op) ?
                univariate_operator_to_id[op] :
                r.univariate_operator_to_id[op] +
                USER_UNIVAR_OPERATOR_ID_START - 1
            push!(nd, NodeData(CALLUNIVAR, id, parentid))
        elseif op in comparison_operators
            push!(
                nd,
                NodeData(COMPARISON, comparison_operator_to_id[op], parentid),
            )
        else
            id =
                haskey(operator_to_id, op) ? operator_to_id[op] :
                r.multivariate_operator_to_id[op] + USER_OPERATOR_ID_START - 1
            push!(nd, NodeData(CALL, id, parentid))
        end
        for k in 2:length(ex.args)
            expr_to_nodedata(ex.args[k], nd, values, myid, r)
        end
    elseif isexpr(ex, :ref)
        @assert ex.args[1] == :x
        push!(nd, NodeData(VARIABLE, ex.args[2], parentid))
    elseif isexpr(ex, :comparison)
        op = ex.args[2]
        opid = comparison_operator_to_id[op]
        for k in 2:2:length(ex.args)-1
            @assert ex.args[k] == op
        end
        push!(nd, NodeData(COMPARISON, opid, parentid))
        for k in 1:2:length(ex.args)
            expr_to_nodedata(ex.args[k], nd, values, myid, r)
        end
    elseif isexpr(ex, :&&) || isexpr(ex, :||)
        @assert length(ex.args) == 2
        op = ex.head
        opid = logic_operator_to_id[op]
        push!(nd, NodeData(LOGIC, opid, parentid))
        expr_to_nodedata(ex.args[1], nd, values, myid, r)
        expr_to_nodedata(ex.args[2], nd, values, myid, r)
    else
        error("Unrecognized expression $ex: $(ex.head)")
    end
    return nothing
end

function expr_to_nodedata(
    ex::Number,
    nd::Vector{NodeData},
    values::Vector{Float64},
    parentid,
    r::UserOperatorRegistry,
)
    valueidx = length(values) + 1
    push!(values, ex)
    push!(nd, NodeData(VALUE, valueidx, parentid))
    return nothing
end

export expr_to_nodedata

# (i,j) nonzero means there's an edge *from* j to *i*
# since we get a column-oriented matrix, this gives us a fast way to look up the
# edges leaving any node (i.e., the children)
function adjmat(nd::Vector{NodeData})
    len = length(nd)
    I = Vector{Int}(undef, len)
    J = Vector{Int}(undef, len)
    realnz = 0
    for nz in 1:len
        par = nd[nz].parent
        par > 0 || continue
        realnz += 1
        I[realnz] = nz
        J[realnz] = par
    end
    resize!(I, realnz)
    resize!(J, realnz)
    return sparse(I, J, ones(Bool, realnz), len, len)
end

export adjmat


"""
    mutable struct Structure_Basic  <: Str

This contains information for model constructing of a series of basic structures.

# Supported list:
 - structure_sc() (Simple Cubic)
 - structure_bcc() (Body Centered Cubic)
 - structure_fcc() (Face Centered Cubic)
 - structure_dc() (Diamond Cubic)
"""
mutable struct Structure_Basic <: Str
    atom_vec::Matrix{Float64}
    cell_vec::Matrix{Float64}
    atom_type
    atom_charge
    num_atoms
    num_atom_types
    res_name
end

function transform(atom_vec, atom_type, atom_charge)
    num_atoms = size(atom_vec)[1]
    base = convert.(Int64, ones(num_atoms))

    if length(atom_type) == 1
        atom_type = base .* atom_type
        atom_charge = base .* atom_charge
        num_atom_types = 1
    else
        num_atom_types = length(unique(atom_type))
        if length(atom_charge) == 1
            atom_charge = base .* atom_charge
        end
    end

    atom_type, atom_charge, num_atoms, num_atom_types
end

"""
    structure_sc(;atom_type=1, atom_charge=0, lattice_const=1)

Do this will return a Structure_Basic type which contains all information needed to build a SC structure.

# Example
```julia-replr
str = structure_sc(atom_type=1, atom_charge=0, lattice_const=3)
```
"""
function structure_sc(;atom_type=1, atom_charge=0, lattice_const=1)
    atom_vec = [
        0   0   0
    ]
    cell_vec = diag([
        1   1   1
    ] .* lattice_const)
    atom_type, atom_charge, num_atoms, num_atom_types = transform(atom_vec, atom_type, atom_charge)
    res_name = "SC"
    Structure_Basic(atom_vec, cell_vec, atom_type, atom_charge, num_atoms, num_atom_types, res_name)
end

"""
    structure_bcc(;atom_type=1, atom_charge=0, lattice_const=1)

Do this will return a Structure_Basic type which contains all information needed to build a BCC structure.

# Arguments
- `atom_type`: type of each atoms, could be a interger or a vector represent type of all atoms or each atoms repectively.
- `atom_charge`: charge of each atoms, could be a float or a vector, same as `atom_type` Data_Unit: e.
- `lattice_const`: lattice constance of structure

# Example
```julia-repl
str = structure_bcc(atom_type=1, atom_charge=-1, lattice_const=3) # All atom in cell will have same type and charge
```
Or
```julia-replr
str = structure_bcc(atom_type=[1 2], atom_charge=[0 2], lattice_const=3)
```
"""
function structure_bcc(;atom_type=1, atom_charge=0, lattice_const=1)
    atom_vec = [
        0   0   0
        0.5 0.5 0.5
    ]
    cell_vec = diag([
        1   1   1
    ] .* lattice_const)
    atom_type, atom_charge, num_atoms, num_atom_types = transform(atom_vec, atom_type, atom_charge)
    res_name = "BCC"
    Structure_Basic(atom_vec, cell_vec, atom_type, atom_charge, num_atoms, num_atom_types, res_name)
end

"""
    structure_fcc(;atom_type=1, atom_charge=0, lattice_const=1)

Do this will return a Structure_Basic type which contains all information needed to build a FCC structure.

# Arguments
- `atom_type`: type of each atoms, could be a interger or a vector represent type of all atoms or each atoms repectively.
- `atom_charge`: charge of each atoms, could be a float or a vector, same as `atom_type` Data_Unit: e.
- `lattice_const`: lattice constance of structure

# Example
```julia-repl
str = structure_fcc(atom_type=1, atom_charge=-1, lattice_const=3) # All atom in cell will have same type and charge
```
Or
```julia-repl
str = structure_fcc(atom_type=[1 2 3 4], atom_charge=[0 0 0 0], lattice_const=3)
```
"""
function structure_fcc(;atom_type=1, atom_charge=0, lattice_const=1)
    atom_vec = [
        0   0   0
        0   0.5 0.5
        0.5 0   0.5
        0.5 0.5 0
    ]
    cell_vec = diag([
        1   1   1
    ] .* lattice_const)
    atom_type, atom_charge, num_atoms, num_atom_types = transform(atom_vec, atom_type, atom_charge)
    res_name = "FCC"
    Structure_Basic(atom_vec, cell_vec, atom_type, atom_charge, num_atoms, num_atom_types, res_name)
end

"""
    structure_dc(;atom_type=1, atom_charge=0, lattice_const=1)

Do this will return a Structure_Basic type which contains all information needed to build a DC structure.

# Arguments
- `atom_type`: type of each atoms, could be a interger or a vector represent type of all atoms or each atoms repectively.
- `atom_charge`: charge of each atoms, could be a float or a vector, same as `atom_type` Data_Unit: e.
- `lattice_const`: lattice constance of structure

# Example
```julia-repl
str = structure_dc(atom_type=1, atom_charge=-1, lattice_const=3) # All atom in cell will have same type and charge
```
Or
```julia-repl
str = structure_dc(atom_type=[1 2 3 4 5 6 7 8], atom_charge=[0 0 0 0 0 0 0 0], lattice_const=3)
```
"""
function structure_dc(;atom_type=1, atom_charge=0, lattice_const=1)
    atom_vec = [
        0    0    0
        0    0.5  0.5
        0.5  0    0.5
        0.5  0.5  0
        0.25 0.25 0.25
        0.25 0.75 0.75
        0.75 0.25 0.75
        0.75 0.75 0.25
    ]
    cell_vec = diag([
        1   1   1
    ] .* lattice_const)
    atom_type, atom_charge, num_atoms, num_atom_types = transform(atom_vec, atom_type, atom_charge)
    res_name = "DC"
    Structure_Basic(atom_vec, cell_vec, atom_type, atom_charge, num_atoms, num_atom_types, res_name)
end


wf = SpikeSorter.SpikeWaveforms(randn(60,1,1024),cumsum(rand(1024)))
fname = tempname()

write(fname, wf)
wf2 = read(fname, SpikeSorter.SpikeWaveforms)

@test_approx_eq wf.timestamps wf2.timestamps
@test_approx_eq wf.waveforms wf2.waveforms


@testset "507.perfect-number.jl" begin
    @test perfect_number(28)
    @test perfect_number(7) == false
end


module Core

using UnstructuredGrids.Helpers
using UnstructuredGrids.Kernels

import Base: ndims
import Base: show
import Base: ==
import UnstructuredGrids.Kernels: generate_dual_connections
import UnstructuredGrids.Kernels: generate_cell_to_faces
import UnstructuredGrids.Kernels: find_cell_to_faces
import UnstructuredGrids.Kernels: generate_face_to_ftype
import UnstructuredGrids.Kernels: generate_face_to_vertices
import UnstructuredGrids.Kernels: generate_facet_to_isboundary
import UnstructuredGrids.Kernels: generate_face_to_isboundary

export Connections
export RefCell
export UGrid
export VERTEX
export list
export ptrs
export coordinates
export connections
export reffaces
export facetypes
export vtkid
export vtknodes
export celltypes
export refcells
export generate_ftype_to_refface
export generate_grid_graph
export generate_full_grid_graph

struct Connections{L<:AbstractVector{<:Integer},P<:AbstractVector{<:Integer}}
  list::L
  ptrs::P
end

list(c::Connections) = c.list

ptrs(c::Connections) = c.ptrs

function Connections(c::AbstractVector{<:AbstractVector{<:Integer}})
  list, ptrs = generate_data_and_ptrs(c)
  Connections(list,ptrs)
end

function show(io::IO,c::Connections)
  clist = list(c)
  cptrs = ptrs(c)
  ncells = length(cptrs)-1
  for cell in 1:ncells
    a = cptrs[cell]
    b = cptrs[cell+1]-1
    println(io,"$cell -> $(clist[a:b])")
  end
end

function (==)(a::Connections,b::Connections)
  a.list == b.list && a.ptrs == b.ptrs
end

struct RefCell
  ndims::Int
  faces::Vector{Vector{Vector{Int}}}
  facetypes::Vector{Vector{Int}}
  reffaces::Vector{Vector{RefCell}}
  coordinates::Array{Float64,2}
  vtkid::Int
  vtknodes::Vector{Int}
end

ndims(r::RefCell) = r.ndims

connections(r::RefCell, dim::Integer) =r.faces[dim+1]

facetypes(r::RefCell, dim::Integer) = r.facetypes[dim+1]

reffaces(r::RefCell, dim::Integer) =r.reffaces[dim+1]

coordinates(r::RefCell) = r.coordinates

vtkid(r::RefCell) = r.vtkid

vtknodes(r::RefCell) = r.vtknodes

function RefCell(;
  ndims::Int,
  faces::Vector{Vector{Vector{Int}}},
  facetypes::Vector{Vector{Int}} = fill(Int[],ndims),
  reffaces::Vector{Vector{RefCell}} = fill(RefCell[],ndims),
  coordinates::Array{Float64,2} = zeros(ndims,0),
  vtkid::Int = UNSET,
  vtknodes::Vector{Int} = Int[])

  @assert ndims == length(faces)
  @assert ndims == length(facetypes)
  @assert ndims == length(reffaces)
  @assert ndims == size(coordinates,1)

  RefCell( ndims, faces, facetypes, reffaces, coordinates, vtkid, vtknodes)

end

const VERTEX = RefCell(
  ndims = 0, faces = fill([Int[]],0), vtkid = 1, vtknodes = [1] )

struct UGrid{
  C<:Connections,
  T<:AbstractVector{<:Integer},
  X<:AbstractArray{<:Number,2}}

  cells::C
  celltypes::T
  refcells::Vector{RefCell}
  coordinates::X

end

connections(g::UGrid) = g.cells

celltypes(g::UGrid) = g.celltypes

refcells(g::UGrid) = g.refcells

coordinates(g::UGrid) = g.coordinates

function ndims(g::UGrid)
  maximum((ndims(r) for r in refcells(g)))
end

function refconnections(g::UGrid,dim::Integer)
  refconnections(refcells(g),dim)
end

function refconnections(refcells::Vector{RefCell}, dim::Integer)
  [ connections(refcell,dim) for refcell in refcells ]
end

function UGrid( cellsdata, cellsptrs, celltypes, refcells, coordinates)
  cells = Connections(cellsdata, cellsptrs)
  UGrid( cells, celltypes, refcells, coordinates)
end

function UGrid(r::RefCell;dim::Integer)
  cells = connections(r,dim)
  celltypes = facetypes(r,dim)
  refcells = reffaces(r,dim)
  coords = coordinates(r)
  UGrid( Connections(cells), celltypes, refcells, coords)
end

function UGrid(
  grid::UGrid,
  dim::Integer,
  vertex_to_cells=generate_dual_connections(connections(grid)),
  cell_to_faces=generate_cell_to_faces(dim,grid,vertex_to_cells))
  cell_to_vertices = connections(grid)
  cell_to_ctype = celltypes(grid)
  ctype_to_refcell = refcells(grid)
  ftype_to_refface, ctype_to_lface_to_ftype = generate_ftype_to_refface(dim,ctype_to_refcell)
  face_to_ftype = generate_face_to_ftype(
    cell_to_faces, cell_to_ctype, ctype_to_lface_to_ftype)
  face_to_vertices = generate_face_to_vertices(
    dim, cell_to_vertices, cell_to_faces, cell_to_ctype, ctype_to_refcell)
  point_to_coords = coordinates(grid)
  UGrid(face_to_vertices, face_to_ftype, ftype_to_refface, point_to_coords)
end

function generate_dual_connections(cell_to_faces::Connections)
  cell_to_faces_data = list(cell_to_faces)
  cell_to_faces_ptrs = ptrs(cell_to_faces)
  face_to_cells_data, face_to_cells_ptrs = generate_dual_connections(
  cell_to_faces_data, cell_to_faces_ptrs)
  Connections(face_to_cells_data, face_to_cells_ptrs)
end

function generate_cell_to_faces(
  dim::Integer,
  grid::UGrid,
  vertex_to_cells=generate_dual_connections(connections(grid)))
  cell_to_vertices = connections(grid)
  cell_to_ctype = celltypes(grid)
  ctype_to_refcell = refcells(grid)
  cell_to_faces = generate_cell_to_faces(
      cell_to_vertices, vertex_to_cells, cell_to_ctype, ctype_to_refcell,dim)
  cell_to_faces
end

function generate_cell_to_faces(
  cell_to_vertices::Connections,
  vertex_to_cells::Connections,
  cell_to_ctype::AbstractVector{<:Integer},
  ctype_to_refcell::Vector{RefCell},
  dim::Integer)

  cell_to_vertices_data = list(cell_to_vertices)
  cell_to_vertices_ptrs = ptrs(cell_to_vertices)

  vertex_to_cells_data = list(vertex_to_cells)
  vertex_to_cells_ptrs = ptrs(vertex_to_cells)

  ctype_to_lface_to_lvertices = refconnections(ctype_to_refcell, dim)

  cell_to_faces_data, cell_to_faces_ptrs = generate_cell_to_faces(
    cell_to_vertices_data,
    cell_to_vertices_ptrs,
    ctype_to_lface_to_lvertices,
    cell_to_ctype,
    vertex_to_cells_data,
    vertex_to_cells_ptrs)

  Connections(cell_to_faces_data, cell_to_faces_ptrs)

end

function find_cell_to_faces(
  cell_to_vertices::Connections,
  vertex_to_faces::Connections,
  cell_to_ctype::AbstractVector{<:Integer},
  ctype_to_refcell::Vector{RefCell},
  dim::Integer)

  cell_to_vertices_data = list(cell_to_vertices)
  cell_to_vertices_ptrs = ptrs(cell_to_vertices)

  vertex_to_faces_data = list(vertex_to_faces)
  vertex_to_faces_ptrs = ptrs(vertex_to_faces)

  ctype_to_lface_to_lvertices = refconnections( ctype_to_refcell, dim)

  cell_to_faces_data, cell_to_faces_ptrs = find_cell_to_faces(
    cell_to_vertices_data,
    cell_to_vertices_ptrs,
    ctype_to_lface_to_lvertices,
    cell_to_ctype,
    vertex_to_faces_data,
    vertex_to_faces_ptrs)

  Connections(cell_to_faces_data, cell_to_faces_ptrs)

end

function find_cell_to_faces(grid::UGrid, fgrid::UGrid)
  face_to_vertices = connections(fgrid)
  vertex_to_faces = generate_dual_connections(face_to_vertices)
  dim = ndims(fgrid)
  find_cell_to_faces(grid, vertex_to_faces, dim)
end

function find_cell_to_faces(
  grid::UGrid,
  vertex_to_faces::Connections,
  dim::Integer)

  cell_to_vertices = connections(grid)
  cell_to_ctype = celltypes(grid)
  ctype_to_refcell = refcells(grid)
  find_cell_to_faces(
    cell_to_vertices,
    vertex_to_faces,
    cell_to_ctype,
    ctype_to_refcell,
    dim)
end

function generate_face_to_vertices(
  dim::Integer,
  cell_to_vertices::Connections,
  cell_to_faces::Connections,
  cell_to_ctype::AbstractVector{<:Integer},
  ctype_to_refcell::Vector{RefCell})

  ctype_to_lface_to_lvertices = refconnections( ctype_to_refcell, dim)

  l, p = generate_face_to_vertices(
    list(cell_to_vertices),
    ptrs(cell_to_vertices),
    list(cell_to_faces),
    ptrs(cell_to_faces),
    cell_to_ctype,
    ctype_to_lface_to_lvertices)

  Connections(l,p)

end

function generate_face_to_ftype(
  cell_to_faces::Connections,
  cell_to_ctype::AbstractVector{<:Integer},
  ctype_to_lface_to_ftype::AbstractVector{<:AbstractVector{<:Integer}})
  generate_face_to_ftype(
    list(cell_to_faces),
    ptrs(cell_to_faces),
    cell_to_ctype,
    ctype_to_lface_to_ftype)
end

function generate_ftype_to_refface(dim,ctype_to_refcell)

  i_to_refface = Vector{RefCell}(undef,0)
  nctypes = length(ctype_to_refcell)
  ctype_to_lftype_to_i = Vector{Vector{Int}}(undef,nctypes)

  i = 1
  for (ctype,refcell) in enumerate(ctype_to_refcell)
    lftype_to_refface = reffaces(refcell,dim)
    lftype_to_i = Vector{Int}(undef,length(lftype_to_refface))
    for (lftype,refface) in enumerate(lftype_to_refface)
      push!(i_to_refface,refface)
      lftype_to_i[lftype] = i
      i +=1
    end
    ctype_to_lftype_to_i[ctype] = lftype_to_i
  end

  ftype_to_refface = unique(i_to_refface)
  i_to_ftype = indexin(i_to_refface,ftype_to_refface)

  ctype_to_lftype_to_ftype = copy(ctype_to_lftype_to_i)
  for ctype in 1:length(ctype_to_lftype_to_i)
    for lftype in 1:length(ctype_to_lftype_to_i[ctype])
      i = ctype_to_lftype_to_i[ctype][lftype]
      ftype = i_to_ftype[i]
      ctype_to_lftype_to_ftype[ctype][lftype] = ftype
    end
  end

  ctype_to_lface_to_ftype = Vector{Vector{Int}}(undef,nctypes)
  for (ctype,refcell) in enumerate(ctype_to_refcell)
    lface_to_lftype = facetypes(refcell,dim)
    lftype_to_ftype = ctype_to_lftype_to_ftype[ctype]
    lface_to_ftype = lftype_to_ftype[lface_to_lftype]
    ctype_to_lface_to_ftype[ctype] = lface_to_ftype
  end

  (ftype_to_refface, ctype_to_lface_to_ftype)

end

function generate_facet_to_isboundary(face_to_cells::Connections)
  face_to_cells_ptrs = ptrs(face_to_cells)
  generate_facet_to_isboundary(face_to_cells_ptrs)
end

function generate_face_to_isboundary(
  facet_to_isboundary::AbstractVector{Bool},
  face_to_facets::Connections)

  face_to_facets_data = list(face_to_facets)
  face_to_facets_ptrs = ptrs(face_to_facets)

  generate_face_to_isboundary(
    facet_to_isboundary,
    face_to_facets_data,
    face_to_facets_ptrs)

end

function generate_grid_graph(grid::UGrid)

  D = ndims(grid)
  cell_to_vertices = connections(grid)
  vertex_to_cells = generate_dual_connections(cell_to_vertices)

  T = typeof(cell_to_vertices)
  primal = Vector{T}(undef,D+1)
  dual = Vector{T}(undef,D+1)

  d = 0
  primal[d+1] = cell_to_vertices
  dual[d+1] = vertex_to_cells

  for d in 1:(D-1)
    cell_to_dfaces = generate_cell_to_faces( d, grid, vertex_to_cells)
    dfaces_to_cells = generate_dual_connections(cell_to_dfaces)
    primal[d+1] = cell_to_dfaces
    dual[d+1] = dfaces_to_cells
  end

  (primal, dual)

end

function generate_full_grid_graph(grid::UGrid)

  D = ndims(grid)
  data = Array{Connections}(undef,D+1,D+1)

  primal, dual = generate_grid_graph(grid)
  for d in 0:(D-1)
    data[D+1,d+1] = primal[d+1]
    data[d+1,D+1] = dual[d+1]
  end

  fgrids = [ UGrid(grid,d,dual[0+1],primal[d+1]) for d in 1:(D-1)]
  for d in 1:(D-1)
    fgrid = fgrids[d]
    face_to_vertices = connections(fgrid)
    vertex_to_faces = generate_dual_connections(face_to_vertices)
    data[d+1,0+1] = face_to_vertices
    data[0+1,d+1] = vertex_to_faces
  end

  for d in 1:(D-1)
    fgrid = fgrids[d]
    for j in 1:(d-1)
      vertex_to_jfaces = data[0+1,j+1]
      face_to_jfaces = find_cell_to_faces(fgrid, vertex_to_jfaces, j)
      jface_to_faces = generate_dual_connections(face_to_jfaces)
      data[d+1,j+1] = face_to_jfaces
      data[j+1,d+1] = jface_to_faces
    end
  end

  for d in 0:(D-1)
    dfaces_to_cells = data[d+1,D+1]
    data[d+1,d+1] = _identity_connections(dfaces_to_cells)
  end

  cell_to_vertices = data[D+1,0+1]
  data[D+1,D+1] = _identity_connections(cell_to_vertices)

  data

end

function _identity_connections(dfaces_to_cells)
    ndfaces = length(dfaces_to_cells.ptrs)-1
    Connections( [i for i in 1:ndfaces], [i for i in 1:(ndfaces+1)])
end

# Helpers

end # module Core


# fld-write.jl

using MIRT: fld_write, fld_read
import MIRT: fld_write_data_fix

#using MIRT: ir_test_dir, ir_test_dir!
using Test: @test, @testset, @test_throws, @inferred

#=
"""
`dir = ir_test_dir!(path)`

set default test directory
"""
function ir_test_dir!(path::String)
	ENV["MIRT_TEST_DIR"] = path
end

"""
`dir = ir_test_dir`

return default test directory `ENV["MIRT_TEST_DIR"]`; default is `/tmp`
"""
function ir_test_dir()
#	get(ENV, "MIRT_TEST_DIR", "/tmp")
	try
		return ENV["MIRT_TEST_DIR"]
	catch
		return "/tmp"
	end
end
=#

"""
`fld_write_test1(file, data, ...)`
"""
function fld_write_test1(file, data ; raw::Bool=false, chat::Bool=false, kwarg...)
	@inferred fld_write(file, data ; raw=raw, kwarg...)
	tmp = fld_read(file, chat=chat)
	tmp != data && throw("test failed for file = $file")

	rm(file)
	if raw
		tmp = file[1:(end-4)] * ".raw"
		rm(tmp)
	end
	true
end

# test writing and reading AVS `.fld` files of all key types

dir = mktempdir()
file = joinpath(dir, "fld-write-test.fld")
chat = isinteractive()

@testset "write std" begin
	for dtype in (UInt8, Int16, Int32, Float32, Float64)
		for endian in (:le, :be)
			for raw in (false, true)
				chat && @info "dtype=$dtype endian=$endian raw=$raw"
				data = convert.(dtype, [5:8; 1:4])
				@test fld_write_test1(file, data, endian=endian, raw=raw,
					head = ["dtype=$dtype endian=$endian raw=$raw",],
					check=true, chat=false)
			end
		end
	end
end

@testset "write odd" begin
	for T in (BigFloat, Float16, BigInt, Int64, Bool)
		data = T.([1 0 1])
		@test fld_write_test1(file, data ; check=true, chat=false)
	end
end

@testset "fld convert" begin
#=
	pairs = (
		(BigFloat, Float64),
		(Float16, Float32),
		(BigInt, Int32),
		(Int64, Int32),
		(Bool, UInt8),
	)
	for (in, out) in pairs
		data = in.([1 0 1])
		@test eltype(fld_write_data_fix(data)) == out
	end
=#
	@test_throws ArgumentError fld_write_data_fix(Rational.([1 0 1]))
end

# for future testing of reading "multi" external files
@testset "write multi" begin
#	file = "../tmp2.fld"
#	file = "tmp.fld"
	data = rand(Float32, 7, 9, 2)
	file1 = "$(basename(file)).part1"
	file2 = "$(basename(file)).part2"
	head = [
		"# AVS field file"
		"ndim=3"
		"dim1=7"
		"dim2=9"
		"dim3=2"
		"nspace=3"
		"veclen=1"
		"data=float"
		"field=uniform"
		"variable 1 file=2 filetype=multi skip=0"
		file1
		file2
	]

	open(file, "w") do fid
		for line in head
			println(fid, line)
		end
	end

	file1 = joinpath(dirname(file), file1)
	file2 = joinpath(dirname(file), file2)
	write(file1, data[:,:,1])
	write(file2, data[:,:,2])

#	run(`cat $file`)
#	run(`ls -l $file1`)
#	run(`op range $file`)

	@test_throws String tmp = fld_read(file) # todo some day...
end


# Here we override broadcasting for banded matrices.
# The design is to to exploit the broadcast machinery so that
# banded matrices that conform to the banded matrix interface but are not
# <: AbstractBandedMatrix can get access to fast copyto!, lmul!, rmul!, axpy!, etc.
# using broadcast variants (B .= A, B .= 2.0 .* A, etc.)

abstract type AbstractBlockBandedStyle <: AbstractArrayStyle{2} end
struct BandedBlockBandedStyle <: AbstractBlockBandedStyle end
abstract type AbstractBlockSkylineStyle <: AbstractBlockBandedStyle end
struct BlockSkylineStyle <: AbstractBlockSkylineStyle end
struct BlockBandedStyle <: AbstractBlockSkylineStyle end
BlockSkylineStyle(::Val{2}) = BlockBandedStyle()
BlockBandedStyle(::Val{2}) = BlockBandedStyle()
BandedBlockBandedStyle(::Val{2}) = BandedBlockBandedStyle()

BroadcastStyle(::BlockSkylineStyle, ::BandedBlockBandedStyle) = BlockSkylineStyle()
BroadcastStyle(::BandedBlockBandedStyle, ::BlockSkylineStyle) = BlockSkylineStyle()
BroadcastStyle(::BlockSkylineStyle, ::BlockBandedStyle) = BlockSkylineStyle()
BroadcastStyle(::BlockBandedStyle, ::BlockSkylineStyle) = BlockSkylineStyle()

BroadcastStyle(::BlockBandedStyle, ::BandedBlockBandedStyle) = BlockBandedStyle()
BroadcastStyle(::BandedBlockBandedStyle, ::BlockBandedStyle) = BlockBandedStyle()

BroadcastStyle(::DefaultArrayStyle{2}, ::BlockSkylineStyle) = BlockSkylineStyle()
BroadcastStyle(::BlockSkylineStyle, ::DefaultArrayStyle{2}) = BlockSkylineStyle()

BroadcastStyle(::DefaultArrayStyle{2}, ::BlockBandedStyle) = BlockBandedStyle()
BroadcastStyle(::BlockBandedStyle, ::DefaultArrayStyle{2}) = BlockBandedStyle()

BroadcastStyle(::DefaultArrayStyle{2}, ::BandedBlockBandedStyle) = BandedBlockBandedStyle()
BroadcastStyle(::BandedBlockBandedStyle, ::DefaultArrayStyle{2}) = BandedBlockBandedStyle()

###
# broadcast blockbandwidths
###
_blockbnds(bc) = blocksize(bc) .- 1

_blockbandwidth_u(u, a, ax) = blockisequal(axes(a,1),ax) ? (blockbandwidth(a,1),u) : (blocksize(ax,1)-1,u)
_blockbandwidth_l(l, a, ax) = blockisequal(axes(a,2),ax) ? (l,blockbandwidth(a,2)) : (l,blocksize(ax,1)-1)

_broadcast_blockbandwidths(bnds, _, _) = bnds
_broadcast_blockbandwidths((l,u), a::AbstractVector, (ax1,ax2)) =
    _blockbandwidth_u(u, a, ax1)

function _broadcast_blockbandwidths((l,u), A::AbstractArray, (ax1,ax2))
    if size(A,2) == 1
        _blockbandwidth_u(u, A, ax1)
    elseif size(A,1) == 1
        _blockbandwidth_l(l, A, ax2)
    elseif blockisequal(axes(A), (ax1,ax2))
        blockbandwidths(A) # need to special case vector broadcasting
    else
        _blockbnds(A)
    end
end

blockbandwidths(bc::Broadcasted{<:Union{Nothing,BroadcastStyle},<:Any,typeof(*)}) =
    min.(_broadcast_blockbandwidths.(Ref(_blockbnds(bc)), bc.args, Ref(axes(bc)))...)

blockbandwidths(bc::Broadcasted{<:Union{Nothing,BroadcastStyle},<:Any,typeof(/)}) = _broadcast_blockbandwidths(_blockbnds(bc), first(bc.args), axes(bc))
blockbandwidths(bc::Broadcasted{<:Union{Nothing,BroadcastStyle},<:Any,typeof(\)}) = _broadcast_blockbandwidths(_blockbnds(bc), last(bc.args), axes(bc))

_subblockbnds(bc) = size(bc) .- 1

_broadcast_subblockbandwidths(bnds) = bnds
_broadcast_subblockbandwidths(bnds, _) = bnds
_broadcast_subblockbandwidths((l,u), a::AbstractVector) = (bandwidth(a,1),u)

function _broadcast_subblockbandwidths((l,u), A::AbstractArray)
    size(A,2) == 1 && return (subblockbandwidth(A,1),u)
    size(A,1) == 1 && return (l, subblockbandwidth(A,2))
    subblockbandwidths(A) # need to special case vector broadcasting
end

subblockbandwidths(bc::Broadcasted{<:Union{Nothing,BroadcastStyle},<:Any,typeof(*)}) =
    min.(_broadcast_subblockbandwidths.(Ref(_subblockbnds(bc)), bc.args)...)

subblockbandwidths(bc::Broadcasted{<:Union{Nothing,BroadcastStyle},<:Any,typeof(/)}) = _broadcast_subblockbandwidths(_subblockbnds(bc), first(bc.args))
subblockbandwidths(bc::Broadcasted{<:Union{Nothing,BroadcastStyle},<:Any,typeof(\)}) = _broadcast_subblockbandwidths(_subblockbnds(bc), last(bc.args))

function subblockbandwidths(bc::Broadcasted)
    (a,b) = size(bc)
    bnds = (a-1,b-1)
    _isweakzero(bc.f, bc.args...) && return min.(bnds, max.(_broadcast_subblockbandwidths.(Ref(bnds), bc.args)...))
    bnds
end

# zero is preserved. Take the maximum bandwidth
import BandedMatrices: _isweakzero

function blockbandwidths(bc::Broadcasted)
    (a,b) = size(bc)
    bnds = (a-1,b-1)
    _isweakzero(bc.f, bc.args...) && return min.(bnds, max.(_broadcast_blockbandwidths.(Ref(bnds), bc.args, Ref(axes(bc)))...))
    bnds
end

similar(bc::Broadcasted{BlockBandedStyle}, ::Type{T}) where T = BlockBandedMatrix{T}(undef, axes(bc), blockbandwidths(bc))
similar(bc::Broadcasted{BandedBlockBandedStyle}, ::Type{T}) where T = BandedBlockBandedMatrix{T}(undef, axes(bc), blockbandwidths(bc), subblockbandwidths(bc))

####
# Default to standard Array broadcast
#
# This is because, for example, exp.(B) is not banded
####

copyto!(dest::AbstractArray, bc::Broadcasted{<:AbstractBlockBandedStyle}) =
   copyto!(dest, Broadcasted{DefaultArrayStyle{2}}(bc.f, bc.args, bc.axes))

similar(bc::Broadcasted{<:AbstractBlockBandedStyle}, ::Type{T}) where T =
    similar(Broadcasted{DefaultArrayStyle{2}}(bc.f, bc.args, bc.axes), T)

##
# copyto!
##

checkblocks(A, B) = blockisequal(axes(A), axes(B)) || throw(DimensionMismatch("*"))

function _blockbanded_copyto!(dest::AbstractMatrix, src::AbstractMatrix)
    @boundscheck checkblocks(dest, src)

    dl, du = colblockbandwidths(dest)
    sl, su = colblockbandwidths(src)
    M,N = blocksize(src)
    # Source matrix must fit within bands of destination matrix
    all(dl .≥ min.(sl,Ref(M-1))) && all(du .≥ min.(su,Ref(N-1))) || throw(BandError(dest))

    for J = 1:N
        for K = max(1,J-du[J]):min(J-su[J]-1,M)
            zero!(view(dest,Block(K),Block(J)))
        end
        for K = max(1,J-su[J]):min(J+sl[J],M)
            copyto!(view(dest,Block(K),Block(J)), view(src,Block(K),Block(J)))
        end
        for K = max(1,J+sl[J]+1):min(J+dl[J],M)
            zero!(view(dest,Block(K),Block(J)))
        end
    end
    dest
end

function blockbanded_copyto!(dest::AbstractMatrix, src::AbstractMatrix)
    if isblockbanded(dest)
        _blockbanded_copyto!(dest, src)
    else
        _blockbanded_copyto!(PseudoBlockArray(dest, axes(src)), src)
    end
    dest
end

_copyto!(_, ::AbstractBlockBandedLayout, dest::AbstractMatrix, src::AbstractMatrix) = blockbanded_copyto!(dest, src)
function _copyto!(_, ::BlockLayout{<:BandedColumns}, dest::AbstractMatrix, src::AbstractMatrix)
    if !blockisequal(axes(dest), axes(src))
        copyto!(PseudoBlockArray(dest, axes(src)), src)
        return dest
    end

    srcB = blocks(src)
    srcD = bandeddata(srcB)

    dl, du = colblockbandwidths(dest)
    sl, su = bandwidths(srcB)
    M,N = size(srcB)
    # Source matrix must fit within bands of destination matrix
    all(dl .≥ min(sl,M-1)) && all(du .≥ min(su,N-1)) || throw(BandError(dest))

    for J = 1:N
        for K = max(1,J-du[J]):min(J-su-1,M)
            zero!(view(dest,Block(K),Block(J)))
        end
        for K = max(1,J-su):min(J+sl,M)
            copyto!(view(dest,Block(K),Block(J)), srcD[K-J+su+1,J])
        end
        for K = max(1,J+sl+1):min(J+dl[J],M)
            zero!(view(dest,Block(K),Block(J)))
        end
    end
    dest
end

function _copyto!(_, ::BlockLayout{<:AbstractBandedLayout}, dest::AbstractMatrix, src::AbstractMatrix)
    if !blockisequal(axes(dest), axes(src))
        copyto!(PseudoBlockArray(dest, axes(src)), src)
        return dest
    end

    srcB = blocks(src)

    dl, du = colblockbandwidths(dest)
    sl, su = bandwidths(srcB)
    M,N = size(srcB)
    # Source matrix must fit within bands of destination matrix
    all(dl .≥ min(sl,M-1)) && all(du .≥ min(su,N-1)) || throw(BandError(dest))

    for J = 1:N
        for K = max(1,J-du[J]):min(J-su-1,M)
            zero!(view(dest,Block(K),Block(J)))
        end
        for K = max(1,J-su):min(J+sl,M)
            copyto!(view(dest,Block(K),Block(J)), inbands_getindex(srcB, K, J))
        end
        for K = max(1,J+sl+1):min(J+dl[J],M)
            zero!(view(dest,Block(K),Block(J)))
        end
    end
    dest
end

function _copyto!(::BandedBlockBandedColumns, ::BandedBlockBandedColumns, dest::AbstractMatrix, src::AbstractMatrix)
    if blockbandwidths(dest) == blockbandwidths(src) && subblockbandwidths(dest) == subblockbandwidths(src)
        copyto!(bandedblockbandeddata(dest), bandedblockbandeddata(src))
        dest
    else # TODO: if subblockbandwidths match we can still do something better
        _copyto!(BandedBlockBandedLayout(), BandedBlockBandedLayout(), dest, src)
    end
end

function copyto!(dest::AbstractArray, bc::Broadcasted{<:AbstractBlockBandedStyle, <:Any, typeof(identity)})
    (A,) = bc.args
    dest ≡ A && return dest
    blockbanded_copyto!(dest, A)
end

##
# lmul!/rmul!
##

######
# extra marrix routines
#####

function blockbanded_fill!(B::AbstractMatrix{T}, x) where T
    x == zero(T) || throw(BandError(B))

    for J = blockaxes(B,2), K = blockcolsupport(B,J)
        fill!(view(B,K,J), x)
    end
    B
end

function blockbanded_rmul!(B::AbstractMatrix{T}, x::Number) where T
    x == zero(T) || throw(BandError(B))

    for J = blockaxes(B,2), K = blockcolsupport(B,J)
        rmul!(view(B,K,J), x)
    end
    B
end

function blockbanded_lmul!(x::Number, B::AbstractMatrix{T}) where T
    x == zero(T) || throw(BandError(B))

    for J = blockaxes(B,2), K = blockcolsupport(B,J)
        lmul!(x, view(B,K,J))
    end
    B
end

function blockbanded_fill!(B::AbstractBlockBandedMatrix, x::Number)
    x == zero(T) || throw(BandError(B))

    fill!(B.data, x)
end

function blockbanded_rmul!(A::AbstractBlockBandedMatrix, x::Number)
    rmul!(A.data, x)
    A
end

function blockbanded_lmul!(x::Number, A::AbstractBlockBandedMatrix)
    lmul!(x, A.data)
    A
end

lmul!(x::Number, A::AbstractBlockBandedMatrix) = blockbanded_lmul!(x, A)
rmul!(A::AbstractBlockBandedMatrix, x::Number) = blockbanded_rmul!(A, x)

function copyto!(dest::AbstractArray, bc::Broadcasted{<:AbstractBlockBandedStyle, <:Any, typeof(-), <:Tuple{<:AbstractMatrix}})
    A, = bc.args
    dest ≡ A || copyto!(dest, A)
    blockbanded_lmul!(-1, dest)
end

similar(bc::Broadcasted{<:AbstractBlockBandedStyle, <:Any, typeof(-), <:Tuple{<:AbstractMatrix}}, ::Type{T}) where T =
    similar(bc.args[1], T)

function copyto!(dest::AbstractArray, bc::Broadcasted{<:AbstractBlockBandedStyle, <:Any, typeof(*), <:Tuple{<:Number,<:AbstractMatrix}})
    α,A = bc.args
    dest ≡ A || copyto!(dest, A)
    blockbanded_lmul!(α, dest)
end

similar(bc::Broadcasted{<:AbstractBlockBandedStyle, <:Any, typeof(*), <:Tuple{<:Number,<:AbstractMatrix}}, ::Type{T}) where T =
    similar(bc.args[2], T)

function copyto!(dest::AbstractArray, bc::Broadcasted{<:AbstractBlockBandedStyle, <:Any, typeof(*), <:Tuple{<:AbstractMatrix,<:Number}})
    A,α = bc.args
    dest ≡ A || copyto!(dest, A)
    blockbanded_rmul!(dest, α)
end

similar(bc::Broadcasted{<:AbstractBlockBandedStyle, <:Any, typeof(*), <:Tuple{<:AbstractMatrix,<:Number}}, ::Type{T}) where T =
    similar(bc.args[1], T)

function copyto!(dest::AbstractArray, bc::Broadcasted{<:AbstractBlockBandedStyle, <:Any, typeof(/), <:Tuple{<:AbstractMatrix,<:Number}})
    A,α = bc.args
    dest ≡ A || copyto!(dest, A)
    blockbanded_rmul!(dest, inv(α))
end

similar(bc::Broadcasted{<:AbstractBlockBandedStyle, <:Any, typeof(/), <:Tuple{<:AbstractMatrix,<:Number}}, ::Type{T}) where T =
    similar(bc.args[1], T)

function copyto!(dest::AbstractArray, bc::Broadcasted{<:AbstractBlockBandedStyle, <:Any, typeof(\), <:Tuple{<:Number,<:AbstractMatrix}})
    α,A = bc.args
    dest ≡ A || copyto!(dest, A)
    blockbanded_lmul!(inv(α), dest)
end

similar(bc::Broadcasted{<:AbstractBlockBandedStyle, <:Any, typeof(\), <:Tuple{<:Number,<:AbstractMatrix}}, ::Type{T}) where T =
    similar(bc.args[2], T)

##
# axpy!
##

# these are the routines of the banded interface of other AbstractMatrices
function blockbanded_axpy!(a, X::AbstractMatrix, Y::AbstractMatrix)
    size(X) == size(Y) || throw(DimensionMismatch())

    for J = blockaxes(X,2), K = blockcolsupport(X,J)
        view(Y,K,J) .= a .* view(X,K,J) .+ view(Y,K,J)
    end
    Y
end

for op in (:+, :-)
    @eval function copyto!(C::AbstractArray{T}, bc::Broadcasted{<:AbstractBlockBandedStyle, <:Any, typeof($op),
                                                                <:Tuple{<:AbstractMatrix,<:AbstractMatrix}}) where T
        bc_axes = Base.Broadcast.combine_axes(bc.args...)# Use combine_axes as `bc` might get axes from `C`
        if !blockisequal(axes(C), bc_axes)
            copyto!(PseudoBlockArray(C, bc_axes), bc)
            return C
        end

        A,B = bc.args
        if !blockisequal(axes(A), axes(B))
            copyto!(C, Base.broadcasted(BlockStyle{2}(), $op, A, B))
            return C
        end

        A_l,A_u = blockbandwidths(A)
        B_l,B_u = blockbandwidths(B)
        C_l,C_u = blockbandwidths(C)

        N,M = blocksize(C)

        for J̃ = 1:M
            J = Block(J̃)
            # joint block range
            for K = Block.(max(1,J̃-min(A_u,B_u)):min(N,J̃+min(A_l,B_l)))
                view(C,K,J) .= $op.(view(A,K,J), view(B,K,J))
            end
            # above, A but not B
            for K = Block.(max(1,J̃-A_u):min(N,J̃-B_u-1))
                view(C,K,J) .= view(A,K,J)
            end
            # above, B but not A
            for K = Block.(max(1,J̃-B_u):min(N,J̃-A_u-1))
                view(C,K,J) .= $op.(view(B,K,J))
            end
            # below, A but not B
            for K = Block.(max(1,J̃+B_l+1):min(N,J̃+A_l))
                view(C,K,J) .= view(A,K,J)
            end
            # below, B but not A
            for K = Block.(max(1,J̃+A_l+1):min(N,J̃+B_l))
                view(C,K,J) .= $op.(view(B,K,J))
            end
            # above, neither
            for K = Block.(max(J̃-C_u,1):min(J̃-max(A_u,B_u)-1,N))
                view(C,K,J) .= zero(T)
            end
            # below, neither
            for K = Block.(max(J̃+max(A_l,B_l)+1,1):min(J̃+C_l,N))
                view(C,K,J) .= zero(T)
            end
        end

        C
    end
end

##
# special cases to hack tests where 2.0 .* A .+ B can be constructed in different ways depending on REPL or in a function.
# Should be made more general.
##

function copyto!(dest::AbstractArray{T}, bc::Broadcasted{<:AbstractBlockBandedStyle, <:Any, typeof(+),
                                                        <:Tuple{<:Broadcasted{<:AbstractBlockBandedStyle,<:Any,typeof(*),<:Tuple{<:Number,<:AbstractMatrix}},
                                                        <:AbstractMatrix}}) where T
    αA,B = bc.args
    α,A = αA.args
    dest ≡ B || blockbanded_copyto!(dest, B)
    blockbanded_axpy!(α, A, dest)
end

function similar(bc::Broadcasted{BlockBandedStyle, <:Any, typeof(+),
                        <:Tuple{<:Broadcasted{<:AbstractBlockBandedStyle,<:Any,typeof(*),<:Tuple{<:Number,<:AbstractMatrix}},
                        <:AbstractMatrix}}, ::Type{T}) where T
    αA,B = bc.args
    α,A = αA.args
    Al,Au = blockbandwidths(A)
    Bl,Bu = blockbandwidths(B)
    BlockBandedMatrix{T}(undef, axes(bc), (max(Al,Bl), max(Au,Bu)))
end

function similar(bc::Broadcasted{BandedBlockBandedStyle, <:Any, typeof(+),
                        <:Tuple{<:Broadcasted{<:AbstractBlockBandedStyle,<:Any,typeof(*),<:Tuple{<:Number,<:AbstractMatrix}},
                        <:AbstractMatrix}}, ::Type{T}) where T
    αA,B = bc.args
    α,A = αA.args
    Al,Au = blockbandwidths(A)
    Bl,Bu = blockbandwidths(B)
    Aλ,Aμ = subblockbandwidths(A)
    Bλ,Bμ = subblockbandwidths(B)

    BandedBlockBandedMatrix{T}(undef, axes(bc), (max(Al,Bl), max(Au,Bu)), (max(Aλ,Bλ), max(Aμ,Bμ)))
end

####
# Special case for Diagonal multiplicartion
####


using Flux: testmode!

@testset "Dropout" begin
  x = [1.,2.,3.]
  @test x == testmode!(Dropout(0.1))(x)
  @test x == Dropout(0)(x)
  @test zeros(x) == Dropout(1)(x)

  x = rand(100)
  m = Dropout(0.9)
  y = m(x)
  @test count(a->a==0, y) > 50
  testmode!(m)
  y = m(x)
  @test count(a->a==0, y) == 0
  testmode!(m, false)
  y = m(x)
  @test count(a->a==0, y) > 50

  x = rand(100)
  m = Chain(Dense(100,100),
            Dropout(0.9))
  y = m(x)
  @test count(a->a == 0, y) > 50
  testmode!(m)
  y = m(x)
  @test count(a->a == 0, y) == 0
end


# ******************************************************************************************
# Notices:
#
# Copyright © 2022 United States Government as represented by the Administrator of the
# National Aeronautics and Space Administration.  All Rights Reserved.
#
# Disclaimers
#
# No Warranty: THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY KIND,
# EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
# THAT THE SUBJECT SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY
# WARRANTY THAT THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT DOCUMENTATION,
# IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE. THIS AGREEMENT DOES NOT, IN ANY MANNER,
# CONSTITUTE AN ENDORSEMENT BY GOVERNMENT AGENCY OR ANY PRIOR RECIPIENT OF ANY RESULTS,
# RESULTING DESIGNS, HARDWARE, SOFTWARE PRODUCTS OR ANY OTHER APPLICATIONS RESULTING FROM
# USE OF THE SUBJECT SOFTWARE.  FURTHER, GOVERNMENT AGENCY DISCLAIMS ALL WARRANTIES AND
# LIABILITIES REGARDING THIRD-PARTY SOFTWARE, IF PRESENT IN THE ORIGINAL SOFTWARE, AND
# DISTRIBUTES IT "AS IS."
#
# Waiver and Indemnity:  RECIPIENT AGREES TO WAIVE ANY AND ALL CLAIMS AGAINST THE UNITED
# STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY PRIOR RECIPIENT.
# IF RECIPIENT'S USE OF THE SUBJECT SOFTWARE RESULTS IN ANY LIABILITIES, DEMANDS, DAMAGES,
# EXPENSES OR LOSSES ARISING FROM SUCH USE, INCLUDING ANY DAMAGES FROM PRODUCTS BASED ON,
# OR RESULTING FROM, RECIPIENT'S USE OF THE SUBJECT SOFTWARE, RECIPIENT SHALL INDEMNIFY AND
# HOLD HARMLESS THE UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL
# AS ANY PRIOR RECIPIENT, TO THE EXTENT PERMITTED BY LAW.  RECIPIENT'S SOLE REMEDY FOR ANY
# SUCH MATTER SHALL BE THE IMMEDIATE, UNILATERAL TERMINATION OF THIS AGREEMENT.
# ******************************************************************************************

"""
    num_leaves(cell::Cell)

Count number of leaves below cell.
"""
num_leaves(cell::Cell) = sum(1 for _ in allleaves(cell))

"""
    max_depth(cell::Cell)

Determine maximum depth below cell.
"""
max_depth(cell::Cell) = isleaf(cell) ? 1 : 1 + maximum(max_depth.(children(cell)))

"""
Total volume of cell proven to satisfy condition or complement.
"""
function proven_volume(cell::Cell, member::Bool=true)
    vol = 0.0
    for l in allleaves(cell)
        if l.data.proven && l.data.member == member
            vol += prod(l.boundary.widths)
        end
    end
    return vol
end

"""
    coverage(cell::Cell, member::Union{Bool,Nothing}=nothing)

Proportion of cell volume proven to satisfy condition, complement, or either.
"""
function coverage(cell::Cell, member::Union{Bool,Nothing}=nothing)
    if member === nothing
        vol = proven_volume(cell, true) + proven_volume(cell, false)
    else
        vol = proven_volume(cell, member)
    end

    return vol / prod(cell.boundary.widths)
end

"""
Compute balance of tree over processors, scaled to [0, 1].
"""
function balance_score(cell::Cell)
    nworkers() == 0 && return NaN

    counts = [0 for _ in workers()]
    for c in allcells(cell)
        if c.data.pid > 0
            counts[c.data.pid - 1] += 1
        end
    end

    dist = counts / sum(counts)
    n = length(dist)
    d = sqrt(sum(dist.^2))
    return sqrt(n) * (1 - d) / (sqrt(n) - 1)
end

"""
Compute size of tree in memory and return formatted variables.
"""
function memory_size(cell::Cell)
    sz = Base.summarysize(cell)
    prefixes = ["G", "M", "k", ""]
    sizes = [1_000_000_000, 1_000_000, 1_000, 1]
    val, pre = 0.0, ""

    for (s, p) in zip(sizes, prefixes)
        if sz >= s
            val = sz / s
            pre = p
            break
        end
    end
    return val, pre
end

"""
    print_metrics(cell::Cell)

Print metrics about proof coverage and tree size.
"""
function print_metrics(cell::Cell)
    vtrue = coverage(cell, true)
    vfalse = coverage(cell, false)
    nleaves = num_leaves(cell)
    val, pre = memory_size(cell)
    dep = max_depth(cell)
    bal = balance_score(cell)

    println("Vol. proportion proven true  : $(vtrue)")
    println("Vol. proportion proven false : $(vfalse)")
    println("Vol. proportion unproven     : $(1 - vtrue - vfalse)")
    println("Total size of spanning tree  : $(nleaves) leaves")
    println("Total size of tree in memory : $val $(pre)B")
    println("Maximum depth of tree        : $dep")
    println("Processor balancing score    : $bal")
end


module UCTransition
#
# This module models the transition from Legacy Means Tested benefits
# to Universal Credit.
#
# Currently it's based crudely on Scotland-wide transition figures from HoC Research
# https://commonslibrary.parliament.uk/constituency-data-universal-credit-roll-out/#caseload
#

using ScottishTaxBenefitModel
using .RunSettings  # : Settings, MT_Routing
using .Intermediate: MTIntermediate, HHIntermed
using .ModelHousehold
using .Definitions
using .Randoms: testp
using .Results: tozero!, HouseholdResult, BenefitUnitResult, LMTResults, UCResults
using .STBIncomes

export
    route_to_uc_or_legacy,
    route_to_uc_or_legacy!

@enum ClaimantType trans_all trans_housing trans_w_kids trans_incapacity trans_jobseekers

const PROPS_ON_UC = Dict(
    trans_all => 0.59,
    trans_housing => 0.61,
    trans_w_kids => 0.55,
    trans_incapacity => 0.32,
    trans_jobseekers => 0.95 )

"""
Allocate a benefit unit to ether UC or Legacy Benefits. Very crudely.
"""
function route_to_uc_or_legacy(
    settings :: Settings,
    bu       :: BenefitUnit,
    intermed :: MTIntermediate ) :: LegacyOrUC
    if settings.means_tested_routing != modelled_phase_in
        return settings.means_tested_routing == uc_full ? uc_bens : legacy_bens
    end
    prob = 0.0
    if intermed.num_job_seekers > 0
        prob = PROPS_ON_UC[trans_jobseekers]
    elseif intermed.limited_capacity_for_work
        prob = PROPS_ON_UC[trans_incapacity]
    elseif intermed.num_children > 0
        prob = PROPS_ON_UC[trans_w_kids]
    elseif intermed.benefit_unit_number == 1
        prob = PROPS_ON_UC[trans_housing]
    else
        prob = PROPS_ON_UC[trans_all]
    end
    head = get_head( bu )
    switch = testp( head.onerand, prob, Randoms.UC_TRANSITION )
    return switch ? uc_bens : legacy_bens
end

function route_to_uc_or_legacy!(
    results  :: HouseholdResult,
    settings :: Settings,
    hh       :: Household,
    intermed :: HHIntermed )
    bus = get_benefit_units( hh )
    RT = eltype( results.income ) # FIXME whole where RT thing

    for bno in eachindex( bus )
        im = intermed.buint[bno]
        bres = results.bus[bno]
        if bres.uc.basic_conditions_satisfied # FIXME This condition needs some thought.
            bres.route = route_to_uc_or_legacy( settings, bus[bno], im )
            if bres.route == legacy_bens
                tozero!( bres, UNIVERSAL_CREDIT )
                bres.uc = UCResults{RT}()
                if( bno == 1 ) && ( bres.legacy_mtbens.ctr_recipient > 0 )# ctr assignment hack
                    bres.pers[bres.legacy_mtbens.ctr_recipient].income[COUNCIL_TAX_BENEFIT] = bres.legacy_mtbens.ctr
                else
                    tozero!( bres, COUNCIL_TAX_BENEFIT )
                end
            elseif bres.route == uc_bens
                # nuke every old thing for everyone who's in scope for UC
                tozero!( bres, LEGACY_MTBS...)
                bres.legacy_mtbens = LMTResults{RT}()
                if( bno == 1 ) && ( bres.uc.recipient > 0 )# ctr assignment hack
                    bres.pers[bres.uc.recipient].income[COUNCIL_TAX_BENEFIT] = bres.uc.ctr
                else
                    tozero!( bres, COUNCIL_TAX_BENEFIT )
                end
                ## FIXME TRANSITIONAL PAYMENTS
            end
        end
        # .. so some futher nuking ..
        if settings.means_tested_routing == uc_full
            # these cease to exist, even for pensioners and students
            tozero!( bres,
                NON_CONTRIB_EMPLOYMENT_AND_SUPPORT_ALLOWANCE,
                NON_CONTRIB_JOBSEEKERS_ALLOWANCE,
                INCOME_SUPPORT,
                WORKING_TAX_CREDIT,
                CHILD_TAX_CREDIT )
            bres.legacy_mtbens = LMTResults{RT}()
            # this last is entirely arbitrary, but
            # takes out essentially all ft students, etc.
            if im.age_oldest_adult < 50
                tozero!( bres, LEGACY_MTBS...)
            end
        end
    end
end

end # Module UCTransition


function transfer_to_plane(ray, t)
    return transfer_to_intersection!(ray, t, Sphere(zero(t)))
end

function n_surfaces(lens::Lens)
    return sum(map(c -> length(c.surfaces), lens.components))
end

"""
Rreallocate a result for trace!
"""
function gen_result(::Type{Array{Ray{T},1}}, lens::Lens{T}) where {T}
    [
        Ray(zero(T), zero(T), zero(T), zero(T), zero(T))
        for i in range(1, stop = 2 + n_surfaces(lens))
    ]
end

"""
Update a result with data from ray tracing and return it
"""
function update_result!(result::Array{Ray{T},1}, index, _symbol, ray) where {T}
    result[index] = Ray(ray.x, ray.y, ray.z, ray.cx, ray.cy)
    return result
end

"""
trace(lens, ray, wavelength, result)

lens :: Lens
ray :: Ray
wavelength :: Real
result :: something that has an update_result! function
ignore_apertures :: Bool
"""
function trace!(result, lens, ray::Ray{T}, wavelength; ignore_apertures = false) where {T}
    wl = convert(typeof(lens.object.t), wavelength)
    ray_c = to_internal(typeof(lens.object.t), ray)
    result = update_result!(result, 1, :object, ray_c)

    global_index = 2
    n = lens.object.n
    t = lens.object.t

    after_surfaces = trace_components!(
        result,
        global_index,
        ray_c,
        n,
        t,
        lens.components,
        wl,
        ignore_apertures,
    )
    trace_to_image!(result, lens, after_surfaces)
end

function trace_components!(
    result,
    global_index,
    ray,
    n,
    t,
    components,
    wavelength,
    ignore_apertures,
)
    if length(components) > 0
        after_surfaces = trace_component!(
            result,
            global_index,
            ray,
            n,
            t,
            components[1],
            wavelength,
            ignore_apertures,
        )
        for c in components[2:end]
            if iserror(after_surfaces)
                return after_surfaces
            end
            (global_index, ray_before, n, t) = after_surfaces
            after_surfaces = trace_component!(
                result,
                global_index,
                ray_before,
                n,
                t,
                c,
                wavelength,
                ignore_apertures,
            )
        end
        after_surfaces
    else
        result
    end
end

function trace_component!(
    result,
    global_index,
    ray_before,
    n,
    t,
    component,
    wavelength,
    ignore_apertures,
)
    trace_surfaces!(
        result,
        global_index,
        component.id,
        ray_before,
        n,
        t,
        component.surfaces,
        wavelength,
        ignore_apertures,
    )
end

function trace_surfaces!(
    result,
    global_index,
    component_id,
    ray_before,
    n,
    t,
    surfaces,
    wavelength,
    ignore_apertures,
)
    for i in 1:length(surfaces)
        s = surfaces[i]
        ray_after = transfer_and_refract(ray_before, n, t, s.surface, s.n, wavelength)
        if iserror(ray_after)
            return RaytraceError(ray_after, global_index, component_id, i, s.id, result)
        end
        result = update_result!(result, global_index, s.id, ray_after)
        if ignore_apertures || !is_vignetted(ray_after, s.aperture)
            global_index = global_index + 1
            ray_before = ray_after
            n = s.n
            t = s.t
        else
            return RaytraceError(
                vignetted_error(),
                global_index,
                component_id,
                i,
                s.id,
                result,
            )
        end
    end
    s_last = surfaces[end]
    (global_index, ray_before, s_last.n, s_last.t)
end

function trace_to_image!(result, lens, (global_index, ray_before, last_n, last_t))
    ray_after = transfer_to_plane(ray_before, last_t)
    update_result!(result, global_index, :image, ray_after)
end

function trace_to_image!(result, lens, err::RaytraceError)
    err
end