using Base: _maxlength, Bool
using FileIO

# run(`cd GameOfLife`)
files = readdir()
txt_files = filter(endswith(".cells"), files)
for (i, file) in enumerate(txt_files)
    # println(typeof(file))
    filename = split(file, ".")[1]
    cp(file, "$(filename).txt", force = true)
end

content = []
open("input.txt") do f
    line = 0
    global content = []
    while !eof(f)
        s = readline(f)
        if length(s) != 0 && s[1] == '!'
            continue
        else
            line += 1
            println("$s")
        end
        if length(s) >= maxlength
            global maxlength = length(s)
        end
        push!(content,s)
    end

end
# close(f)

finalresult = BitArray[]
for i in 1:length(content)
    global finalresult;
    local1 = []
    if length(content[i])<maxlength
        content[i]=rpad.(content[i],maxlength,"0")
    end
    for j in 1:maxlength
        if content[i][j] == 'O'
            push!(local1,1)
        else
            push!(local1,0)
        end
    end
    push!(finalresult, local1)
end
as = finalresult
println(as)
final = open("result.js", "w")
array = "$as"
# writing to a file using write() method
write(final, "var output= $(array[9:end])")
close(final)


const IDType = UInt64

"""
An `Index` represents a single tensor index with fixed dimension `dim`. Copies of an Index compare equal unless their
`tags` are different.

An Index carries a `TagSet`, a set of tags which are small strings that specify properties of the `Index` to help
distinguish it from other Indices. There is a special tag which is referred to as the integer tag or prime
level which can be incremented or decremented with special priming functions.

Internally, an `Index` has a fixed `id` number, which is how the ITensor library knows two indices are copies of a
single original `Index`. `Index` objects must have the same `id`, as well as the `tags` to compare equal.
"""
struct Index{T}
  id::IDType
  space::T
  dir::Arrow
  tags::TagSet
  plev::Int
  function Index{T}(id, space::T, dir, tags, plev) where {T}
    return new{T}(id, space, dir, tags, plev)
  end
end

# Used in NDTensors for generic code,
# mostly for internal usage
Index{T}(dim::T) where {T} = Index(dim)

function Index(id, space::T, dir, tags, plev) where {T}
  return Index{T}(id, space, dir, tags, plev)
end

"""
    Index(dim::Int; tags::Union{AbstractString, TagSet} = "",
                    plev::Int = 0)

Create an `Index` with a unique `id`, a TagSet given by `tags`,
and a prime level `plev`.

# Examples
```jldoctest; filter=r"id=[0-9]{1,3}"
julia> i = Index(2; tags = "l", plev = 1)
(dim=2|id=818|"l")'

julia> dim(i)
2

julia> plev(i)
1

julia> tags(i)
"l"
```
"""
function Index(dim::Int; tags="", plev=0)
  return Index(rand(IDType), dim, Neither, tags, plev)
end

"""
    Index(dim::Integer, tags::Union{AbstractString, TagSet}; plev::Int = 0)

Create an `Index` with a unique `id` and a tagset given by `tags`.

# Examples
```jldoctest; filter=r"id=[0-9]{1,3}"
julia> i = Index(2, "l,tag")
(dim=2|id=58|"l,tag")

julia> dim(i)
2

julia> plev(i)
0

julia> tags(i)
"l,tag"
```
"""
Index(dim::Int,
      tags::Union{AbstractString, TagSet};
      plev::Int = 0) = Index(dim; tags = tags, plev = plev)

"""
    id(i::Index)

Obtain the id of an Index, which is a unique 64 digit integer.
"""
id(i::Index) = i.id

"""
    dim(i::Index)

Obtain the dimension of an Index.

For a QN Index, this is the sum of the block dimensions.
"""
NDTensors.dim(i::Index) = i.space

space(i::Index) = i.space

"""
    dir(i::Index)

Obtain the direction of an Index (In, Out, or Neither).
"""
dir(i::Index) = i.dir

# Used for generic code in NDTensors
NDTensors.dir(i::Index) = dir(i)

"""
    setdir(i::Index, dir::Arrow)

Create a copy of Index i with the specified direction.
"""
function setdir(i::Index, dir::Arrow)
  return Index(id(i),copy(space(i)),dir,copy(tags(i)),plev(i))
end

"""
    tags(i::Index)

Obtain the TagSet of an Index.
"""
tags(i::Index) = i.tags

"""
    plev(i::Index)

Obtain the prime level of an Index.
"""
plev(i::Index) = i.plev

"""
    ==(i1::Index, i1::Index)

Compare indices for equality. First the id's are compared,
then the prime levels are compared, and finally the
tags are compared.
"""
function Base.:(==)(i1::Index, i2::Index)
  return id(i1) == id(i2) &&
         plev(i1) == plev(i2) &&
         tags(i1) == tags(i2)
end

# This is so that when IndexSets are converted
# to Julia Base Sets, the hashing is done correctly
function Base.hash(i::Index, h::UInt)
  return hash((id(i), tags(i), plev(i)), h)
end

"""
    copy(i::Index)

Create a copy of index `i` with identical `id`, `dim`, `dir` and `tags`.
"""
Base.copy(i::Index) = Index(id(i),
                            copy(space(i)),
                            dir(i),
                            tags(i),
                            plev(i))

"""
    sim(i::Index; tags = tags(i), plev = plev(i), dir = dir(i))

Produces an `Index` with the same properties (dimension or QN structure)
but with a new `id`.
"""
sim(i::Index;
    tags=copy(tags(i)),
    plev=plev(i),
    dir=dir(i)) = Index(rand(IDType),
                        copy(space(i)),
                        dir,
                        tags,
                        plev)

# Used for internal use in NDTensors
NDTensors.sim(i::Index) = sim(i)

"""
    dag(i::Index)

Copy an index `i` and reverse its direction.
"""
dag(i::Index) = Index(id(i),
                      copy(space(i)),
                      -dir(i),
                      tags(i),
                      plev(i))

# For internal use in NDTensors
NDTensors.dag(i::Index) = dag(i)

"""
    hastags(i::Index, ts::Union{AbstractString,TagSet})

Check if an `Index` `i` has the provided tags,
which can be a string of comma-separated tags or
a TagSet object.

# Examples
```jldoctest; filter=r"id=[0-9]{1,3}"
julia> i = Index(2, "SpinHalf,Site,n=3")
(dim=2|id=861|"Site,SpinHalf,n=3")

julia> hastags(i, "SpinHalf,Site")
true

julia> hastags(i, "Link")
false
```
"""
hastags(i::Index,
        ts::Union{AbstractString,TagSet}) = hastags(tags(i),ts)

hastags(ts::Union{AbstractString,TagSet}) = x->hastags(x,ts)

"""
    hasplev(i::Index, plev::Int)

Check if an `Index` `i` has the provided prime level.

# Examples
```jldoctest; filter=r"id=[0-9]{1,3}"
julia> i = Index(2; plev=2)
(dim=2|id=543)''

julia> hasplev(i, 2)
true

julia> hasplev(i, 1)
false
```
"""
hasplev(i::Index, pl::Int) = plev(i) == pl

hasplev(pl::Int) = x -> hasplev(x, pl)

"""
    hasid(i::Index, id::ITensors.IDType)

Check if an `Index` `i` has the provided id.

# Examples
```jldoctest; filter=r"id=[0-9]{1,3}"
julia> i = Index(2)
(dim=2|id=321)

julia> hasid(i, id(i))
true

julia> j = Index(2)
(dim=2|id=17)

julia> hasid(i, id(j))
false
```
"""
hasid(ind::Index, i::IDType) = id(ind) == i

hasid(i::IDType) = x -> hasid(x, i)

"""
    settags(i::Index, ts)

Return a copy of Index `i` with
tags replaced by the ones given
The `ts` argument can be a comma-separated
string of tags or a TagSet.

# Examples
```jldoctest; filter=r"id=[0-9]{1,3}"
julia> i = Index(2, "SpinHalf,Site,n=3")
(dim=2|id=543|"Site,SpinHalf,n=3")

julia> hastags(i, "Link")
false

julia> j = settags(i,"Link,n=4")
(dim=2|id=543|"Link,n=4")

julia> hastags(j, "Link")
true

julia> hastags(j, "n=4,Link")
true
```
"""
settags(i::Index, ts) = Index(id(i),
                              copy(space(i)),
                              dir(i),
                              ts,
                              plev(i))

"""
    addtags(i::Index,ts)

Return a copy of Index `i` with the
specified tags added to the existing ones.
The `ts` argument can be a comma-separated
string of tags or a TagSet.
"""
addtags(i::Index, ts) = settags(i, addtags(tags(i), ts))

"""
    removetags(i::Index, ts)

Return a copy of Index `i` with the
specified tags removed. The `ts` argument
can be a comma-separated string of tags or a TagSet.
"""
removetags(i::Index, ts) = settags(i, removetags(tags(i), ts))

"""
    replacetags(i::Index, tsold, tsnew)

If the tag set of `i` contains the tags specified by `tsold`,
replaces these with the tags specified by `tsnew`, preserving
any other tags. The arguments `tsold` and `tsnew` can be
comma-separated strings of tags, or TagSet objects.
"""
replacetags(i::Index,
            tsold,
            tsnew) = settags(i,
                             replacetags(tags(i), tsold, tsnew))

"""
    prime(i::Index, plinc::Int = 1)

Return a copy of Index `i` with its
prime level incremented by the amount `plinc`
"""
prime(i::Index, plinc::Int = 1) = setprime(i, plev(i) + plinc)

"""
    setprime(i::Index, plev::Int)

Return a copy of Index `i` with its
prime level set to `plev`
"""
setprime(i::Index, plev::Int) = Index(id(i),
                                      copy(space(i)),
                                      dir(i),
                                      tags(i),
                                      plev)

"""
    noprime(i::Index)

Return a copy of Index `i` with its
prime level set to zero.
"""
noprime(i::Index) = setprime(i, 0)

"""
    adjoint(i::Index)

Prime an Index using the notation `i'`.
"""
Base.adjoint(i::Index) = prime(i)

"""
    ^(i::Index, pl::Int)

Prime an Index using the notation `i^3`.
"""
Base.:^(i::Index, pl::Int) = prime(i, pl)

"""
Iterating over Index `I` gives the IndexVals `I(1)` through `I(dim(I))`.
"""
function Base.iterate(i::Index, state::Int = 1)
  (state > dim(i)) && return nothing
  return (i(state), state+1)
end

# This is a trivial definition for use in NDTensors
NDTensors.outer(i::Index;
                dir = dir(i),
                tags = "",
                plev::Int = 0) =
  sim(i; tags = tags,
         plev = plev,
         dir = dir)

# This is for use in NDTensors
function NDTensors.outer(i1::Index, i2::Index; tags = "")
  return Index(dim(i1) * dim(i2), tags)
end

#
# QN related functions
#

"""
    hasqns(::Index)

Checks of the Index has QNs or not.
"""
hasqns(::Index) = false

"""
    removeqns(::Index)

Removes the QNs from the Index, if it has any.
"""
removeqns(i::Index) = i

#
# IndexVal
#

"""
An IndexVal represents an Index object set to a certain value.
"""
struct IndexVal{IndexT<:Index}
  ind::IndexT
  val::Int
  function IndexVal(i::IndexT,
                    n::Int) where {IndexT <: Index}
    n>dim(i) && throw(ErrorException("Value $n greater than size of Index $i"))
    dim(i)>0 && n<1 && throw(ErrorException("Index value must be >= 1 (was $n)"))
    dim(i)==0 && n<0 && throw(ErrorException("Index value must be >= 1 (was $n)"))
    return new{IndexT}(i, n)
  end
end

"""
    IndexVal(i::Index, n::Int)

    IndexVal(iv::Pair{<:Index, Int})

    (i::Index)(n::Int)

Create an `IndexVal` from a pair of `Index` and `Int`.

Alternatively, you can use the syntax `i(n)`.
"""
IndexVal(iv::Pair{<:Index,Int}) = IndexVal(iv.first,iv.second)

# Help treat a Pair{IndexT, Int} like an IndexVal{IndexT}
const IndexValOrPairIndexInt{IndexT} = Union{IndexVal{IndexT},
                                             Pair{IndexT, Int}}

Base.convert(::Type{IndexVal}, iv::Pair{<:Index,Int}) = IndexVal(iv)

Base.convert(::Type{IndexVal{IndexT}},
             iv::Pair{IndexT, Int}) where {IndexT<:Index} = IndexVal(iv)

Base.getindex(i::Index, j::Int) = IndexVal(i, j)

(i::Index)(n::Int) = IndexVal(i, n)

"""
    ind(iv::IndexVal)

Return the Index of the IndexVal.
"""
NDTensors.ind(iv::IndexVal) = iv.ind

NDTensors.ind(iv::Pair{<:Index,Int}) = iv.first

"""
    val(iv::IndexVal)

Return the value of the IndexVal.
"""
val(iv::IndexVal) = iv.val

val(iv::Pair{<:Index,Int}) = iv.second

"""
    isindequal(i::Index, iv::IndexVal)

    isindequal(i::IndexVal, iv::Index)

    isindequal(i::IndexVal, iv::IndexVal)

Check if the Index and IndexVal have the same indices.
"""
isindequal(i::Index,
           iv::IndexValOrPairIndexInt) = i == ind(iv)

isindequal(iv::IndexValOrPairIndexInt,
           i::Index) = isindequal(i, iv)

isindequal(iv1::IndexValOrPairIndexInt,
           iv2::IndexValOrPairIndexInt) = ind(iv1) == ind(iv2)

plev(iv::IndexValOrPairIndexInt) = plev(ind(iv))

prime(iv::IndexVal,
      inc::Integer = 1) = IndexVal(prime(ind(iv), inc), val(iv))

dag(iv::IndexVal) = IndexVal(dag(ind(iv)), val(iv))

Base.adjoint(iv::IndexVal) = IndexVal(prime(ind(iv)), val(iv))

#
# Printing, reading, and writing
#

function primestring(plev)
  if plev<0
    return " (warning: prime level $plev is less than 0)"
  end
  if plev==0
    return ""
  elseif plev > 3
    return "'$plev"
  else
    return "'"^plev
  end
end

function Base.show(io::IO,
                   i::Index)
  idstr = "$(id(i) % 1000)"
  if length(tags(i)) > 0
    print(io,"(dim=$(space(i))|id=$(idstr)|\"$(tagstring(tags(i)))\")$(primestring(plev(i)))")
  else
    print(io,"(dim=$(space(i))|id=$(idstr))$(primestring(plev(i)))")
  end
end

Base.show(io::IO, iv::IndexVal) = print(io, ind(iv), "=>$(val(iv))")

function readcpp(io::IO, ::Type{Index}; kwargs...)
  format = get(kwargs, :format, "v3")
  if format != "v3"
    throw(ArgumentError("read Index: format=$format not supported"))
  end
  tags = readcpp(io, TagSet; kwargs...)
  id = read(io, IDType)
  dim = convert(Int64, read(io, Int32))
  dir_int = read(io, Int32)
  dir = dir_int < 0 ? In : Out
  read(io, 8) # Read default IQIndexDat size, 8 bytes
  return Index(id, dim, dir, tags)
end

function HDF5.write(parent::Union{HDF5File, HDF5Group},
                    name::AbstractString,
                    I::Index)
  g = g_create(parent, name)
  attrs(g)["type"] = "Index"
  attrs(g)["version"] = 1
  write(g, "id", id(I))
  write(g, "dim", dim(I))
  write(g, "dir", Int(dir(I)))
  write(g, "tags", tags(I))
  write(g, "plev", plev(I))
  if typeof(space(I)) == Int
    attrs(g)["space_type"] = "Int"
  elseif typeof(space(I)) == QNBlocks
    attrs(g)["space_type"] = "QNBlocks"
    write(g,"space",space(I))
  else
    error("Index space type not recognized")
  end
end

function HDF5.read(parent::Union{HDF5File,HDF5Group},
                   name::AbstractString,
                   ::Type{Index})
  g = g_open(parent,name)
  if read(attrs(g)["type"]) != "Index"
    error("HDF5 group or file does not contain Index data")
  end
  id = read(g,"id")
  dim = read(g,"dim")
  dir = Arrow(read(g,"dir"))
  tags = read(g,"tags",TagSet)
  plev = read(g,"plev")
  space_type = "Int"
  if exists(attrs(g),"space_type")
    space_type = read(attrs(g)["space_type"])
  end
  if space_type == "Int"
    space = dim
  elseif space_type == "QNBlocks"
    space = read(g,"space",QNBlocks)
  end
  return Index(id,space,dir,tags,plev)
end


using SEMPP
using Test, Random, Distributions, EGPD, CSV, DataFrames
using Dates

Random.seed!(12)

@testset "SEMPP.jl" begin
    include("Structures_tests.jl")
    include("ParametersEstimation_tests.jl")
    include(joinpath("Plots_tests", "PlotsData_tests.jl"))
end


module Synchronous

using ..Instantiation

import ModiaMath
import ModiaMath.ModiaToModiaMath  # ModiaSimulationModel, EventRestart, Restart, positive!, ...
import ModiaMath.ModiaToModiaMath.ModiaSimulationModel
import ModiaMath.Restart

@static if VERSION < v"0.7.0-DEV.2005"
    Nothing = Void
end

#= Synchronous Modelica primitives

Clock()
Clock(intervalCounter::Int, resolution::Int=1)  # Add version with rational number
Clock(interval::Float64)
Clock(condition::Bool, startInterval::Float64=0.0)
Clock(c::Clock, solverMethod)

previous(x)

sample(u, c::Clock)
sample(u)

hold(u) = u

subSample(u, factor::Int)
superSample(u, factor::Int)
shiftSample(u, shiftCounter::Int, resolution::Int=1)  # Add version with rational number
backSample(u, backCounter::Int, resolution::Int=1)  # Add version with rational number

noClock()

interval(u)

=#

# The argument list of function F is extended with the argument simulationModel.

const nClock = 100
const nSample = 100
const nPrevious = 100
const nCrossings = 100

mutable struct Store
    clock::Vector{ModiaMath.Time}
    sample::Vector{Any}
    previous::Vector{Any}
    nextPrevious::Vector{Any}
    crossings::Vector{Any}

    @static if VERSION < v"0.7.0-DEV.2005"
      Store() = new(zeros(nClock), Array{Any}(nSample), zeros(nPrevious), zeros(nPrevious), zeros(nCrossings))
    else
      Store() = new(zeros(nClock), Array{Any}(undef, nSample), zeros(nPrevious), zeros(nPrevious), zeros(nCrossings))
    end
end

isLog(m) = true

# -------------------------------------------------------------------

# Temporary:
allInstances(s) = return s

function Clock(interval::ModiaMath.Time, m::ModiaSimulationModel, nr::Int)
    if ModiaToModiaMath.isPreInitial(m)
        if ModiaToModiaMath.isLogEvents(m)
            println("        in Clock, nr = $nr (isPreInitial)")
        end
        return false
    end

    if ModiaToModiaMath.isInitial(m)
        if ModiaToModiaMath.isLogEvents(m)
            println("        in Clock, nr = $nr (isInitial)")
        end

        if typeof(m.store) == Nothing
            m.store = Store()
        end

        m.store.clock[nr] = ModiaMath.getTime(m)
        ModiaToModiaMath.setNextEvent!(m, m.store.clock[nr])

    elseif ModiaToModiaMath.isEvent(m) && ModiaToModiaMath.isAfterSimulationStart(m)
        tick = abs(ModiaMath.getTime(m) - m.store.clock[nr]) < 1E-10
        if tick
            if ModiaToModiaMath.isLogEvents(m)
                println("        in Clock, nr = $nr (isEvent; clock is active)")
            end

            m.store.clock[nr] = ModiaMath.getTime(m) + interval
            ModiaToModiaMath.setNextEvent!(m, m.store.clock[nr])
            return true
        end
        ModiaToModiaMath.setNextEvent!(m, m.store.clock[nr])
    end

    return false
end

function positive(crossing::Float64, m::ModiaSimulationModel, nr::Int; restart::ModiaToModiaMath.EventRestart=Restart)
    if typeof(m.store) == Nothing
        m.store = Store()
    end
        println("positive:"); @show nr m; ModiaToModiaMath.positive!(crossing, m, nr; restart=restart)
end

positiveChange(crossing::Float64, m::ModiaSimulationModel, nr::Int; restart::ModiaToModiaMath.EventRestart=Restart) =
         ModiaToModiaMath.positiveChange!(crossing, m, nr; restart=restart)
positiveEdge(crossing::Float64, m::ModiaSimulationModel, nr::Int; restart::ModiaToModiaMath.EventRestart=Restart) =
         ModiaToModiaMath.positiveEdge!(crossing, m, nr; restart=restart)

function sample(v, clock::Bool, m::ModiaSimulationModel, nr::Int)
    if ModiaToModiaMath.isPreInitial(m)
        if ModiaToModiaMath.isLogEvents(m)
            println("        in sample, nr = $nr (isPreInitial)")
        end
        return v
    elseif ModiaToModiaMath.isInitial(m)
        if ModiaToModiaMath.isLogEvents(m)
            println("        in sample, nr = $nr (initialize sample store)")
        end

        if typeof(m.store) == Nothing
            m.store = Store()
        end

        m.store.sample[nr] = v
        return v
    end

    if clock
        if ModiaToModiaMath.isLogEvents(m)
            println("        in sample, nr = $nr (clock is active)")
        end
        m.store.sample[nr] = v;
        return v
    else
        return m.store.sample[nr]
    end
end

# initPrevious not used
function initPrevious(v, m::ModiaSimulationModel, nr::Int)
    if ModiaToModiaMath.isPreInitial(m)
        if ModiaToModiaMath.isLogEvents(m)
            println("        in initPrevious, nr = $nr (isPreInitial)")
        end
        return v

    elseif ModiaToModiaMath.isInitial(m)
        if ModiaToModiaMath.isLogEvents(m)
            println("        in initPrevious, nr = $nr (initialize previous store)")
        end

        if typeof(m.store) == Nothing
            m.store = Store()
        end

        m.store.previous[nr] = v
        return v
    else
        return m.store.previous[nr]
    end
end

function previous(v, clock::Bool, m::ModiaSimulationModel, nr::Int)
    if ModiaToModiaMath.isPreInitial(m)
        if ModiaToModiaMath.isLogEvents(m)
            println("        in previous, nr = $nr (isPreInitial)")
        end
        return v

    elseif ModiaToModiaMath.isInitial(m)
        if ModiaToModiaMath.isLogEvents(m)
            println("        in previous, nr = $nr (initialize previous store)")
        end

        if typeof(m.store) == Nothing
            m.store = Store()
        end

        m.store.previous[nr]     = v
        m.store.nextPrevious[nr] = v
        return v
    end

    if clock
        m.store.previous[nr] = m.store.nextPrevious[nr];
        if ModiaToModiaMath.isLogEvents(m)
            println("        in previous, nr = $nr (clock is active)")
        end
    end
    return m.store.previous[nr]
end

#=
function previous(v, clock::Bool)
  return v
end
=#

function updatePrevious(v, m::ModiaSimulationModel, nr::Int)
    if ModiaToModiaMath.isInitial(m)
        if typeof(m.store) == Nothing
            m.store = Store()
        end
    end

    if ModiaToModiaMath.isEvent(m) && ModiaToModiaMath.isAfterSimulationStart(m)
    # if ModiaToModiaMath.isLogEvents(m)
    #   println("        in updatePrevious, nr = $nr, v = $v, time = ", ModiaMath.getTime(m))
    # end
        m.store.nextPrevious[nr] = v
    end
end

hold(v) = v

end


# NucleicAcids
# ============
#
# DNA and RNA nucleotide types.
#
# This file is a part of BioJulia.
# License is MIT: https://github.com/BioJulia/Bio.jl/blob/master/LICENSE.md

# NucleicAcid Encoding
# -------------------
#
# Unambiguous nucleotides are represented in one-hot encoding as follows:
#
#   | NucleicAcid | Bits |
#   | ----------- | ---- |
#   |     A       | 0001 |
#   |     C       | 0010 |
#   |     G       | 0100 |
#   |    T/U      | 1000 |
#
# Ambiguous nucleotides are bitwise OR of these four nucleotides. For example, R
# , A or G, is represented as 0101 (= A: 0001 | G: 0100). The gap symbol is
# always 0000.  The meaningful four bits are stored in the least significant
# bits of a byte.

abstract NucleicAcid
bitstype 8 DNA <: NucleicAcid
bitstype 8 RNA <: NucleicAcid

# Conversion from/to integers
# ---------------------------

Base.convert(::Type{DNA}, nt::UInt8) = reinterpret(DNA, nt)
Base.convert(::Type{RNA}, nt::UInt8) = reinterpret(RNA, nt)
Base.convert(::Type{UInt8}, nt::DNA) = reinterpret(UInt8, nt)
Base.convert(::Type{UInt8}, nt::RNA) = reinterpret(UInt8, nt)
Base.convert{T<:Number,S<:NucleicAcid}(::Type{T}, nt::S) = convert(T, UInt8(nt))
Base.convert{T<:Number,S<:NucleicAcid}(::Type{S}, nt::T) = convert(S, UInt8(nt))

# Bit Operations
# --------------

function Base.:~{N<:NucleicAcid}(x::N)
    return reinterpret(N, ~reinterpret(UInt8, x) & 0b1111)
end

function Base.:|{N<:NucleicAcid}(x::N, y::N)
    return reinterpret(N, reinterpret(UInt8, x) | reinterpret(UInt8, y))
end

function Base.:&{N<:NucleicAcid}(x::N, y::N)
    return reinterpret(N, reinterpret(UInt8, x) & reinterpret(UInt8, y))
end

function Base.:-{N<:NucleicAcid}(x::N, y::N)
    return Int(x) - Int(y)
end

function Base.:-{N<:NucleicAcid}(x::N, y::Integer)
    return x + (-y)
end

function Base.:+{N<:NucleicAcid}(x::N, y::Integer)
    return reinterpret(N, (UInt8(x) + y % UInt8) & 0b1111)
end

function Base.isless{N<:NucleicAcid}(x::N, y::N)
    return isless(reinterpret(UInt8, x), reinterpret(UInt8, y))
end

@inline function Base.count_ones(nt::NucleicAcid)
    return count_ones(reinterpret(UInt8, nt))
end

function Base.trailing_zeros(nt::NucleicAcid)
    return trailing_zeros(reinterpret(UInt8, nt))
end

function gap{N<:NucleicAcid}(::Type{N})
    return reinterpret(N, 0b0000)
end

"""
    isGC(nt::NucleicAcid)

Test if `nt` is surely either guanine or cytosine.
"""
function isGC(nt::NucleicAcid)
    bits = reinterpret(UInt8, nt)
    return bits != 0 && (bits & 0b1001) == 0
end

"""
    ispurine(nt::NucleicAcid)

Test if nucleotide is surely a purine.
"""
@inline function ispurine(nt::NucleicAcid)
    bits = reinterpret(UInt8, nt)
    return bits != 0 && (bits & 0b1010) == 0
end

"""
    ispyrimidine(nt::NucleicAcid)

Test if nucleotide is surely a pyrimidine.
"""
@inline function ispyrimidine(nt::NucleicAcid)
    bits = reinterpret(UInt8, nt)
    return bits != 0 && (bits & 0b0101) == 0
end

"""
    isambiguous(nt::NucleicAcid)

Test if `nt` is ambiguous nucleotide.
"""
@inline function isambiguous(nt::NucleicAcid)
    return count_ones(nt) > 1
end

"""
    iscertain(nt::NucleicAcid)

Test if `nt` is a non-ambiguous nucleotide e.g. ATCG.
"""
@inline function iscertain(nt::NucleicAcid)
    return count_ones(nt) == 1
end

"""
    isgap(nt::NucleicAcid)
Test if `nt` is a gap.
"""
@inline function isgap(nt::NucleicAcid)
    return count_ones(nt) == 0
end

"""
    complement(nt::NucleicAcid)

Return the complementary nucleotide of `nt`.
"""
function complement(nt::NucleicAcid)
    bits = compatbits(nt)
    return reinterpret(
        typeof(nt),
        (bits & 0x01) << 3 | (bits & 0x08) >> 3 |
        (bits & 0x02) << 1 | (bits & 0x04) >> 1)
end

function Base.isvalid{T<:NucleicAcid}(::Type{T}, x::Integer)
    return 0 ≤ x < 16
end

function Base.isvalid(nt::NucleicAcid)
    return reinterpret(UInt8, nt) ≤ 0b1111
end

"""
    iscompatible(x, y)

Return `true` if and only if `x` and `y` are compatible with each other (i.e.
`x` and `y` can be the same symbol).

`x` and `y` must be the same type (`DNA`, `RNA` or `AminoAcid`).

# Examples

```julia
julia> iscompatible(DNA_A, DNA_A)
true

julia> iscompatible(DNA_C, DNA_N)  # DNA_N can be DNA_C
true

julia> iscompatible(DNA_C, DNA_R)  # DNA_R (A or G) cannot be DNA_C
false

julia> iscompatible(AA_A, AA_X)    # AA_X can be AA_A
true

```
"""
@inline function iscompatible{T<:NucleicAcid}(x::T, y::T)
    return compatbits(x) & compatbits(y) != 0
end

# Return the compatibility bits of `nt`.
@inline function compatbits(nt::NucleicAcid)
    return reinterpret(UInt8, nt)
end

# NucleicAcid encoding definition
# ------------------------------

# DNA

# lookup table for characters
const char_to_dna = [0x80 for _ in 0x00:0xff]
const dna_to_char = Vector{Char}(16)

# derived from "The DDBJ/ENA/GenBank Feature Table Definition"
# §7.4.1 Nucleotide base code (IUPAC)
# http://www.insdc.org/documents/feature_table.html#7.4.1
for (char, doc, bits) in [
        ('-', "DNA Gap",                                   0b0000),
        ('A', "DNA Adenine",                               0b0001),
        ('C', "DNA Cytosine",                              0b0010),
        ('G', "DNA Guanine",                               0b0100),
        ('T', "DNA Thymine",                               0b1000),
        ('M', "DNA Adenine or Cytosine",                   0b0011),
        ('R', "DNA Adenine or Guanine",                    0b0101),
        ('W', "DNA Adenine or Thymine",                    0b1001),
        ('S', "DNA Cytosine or Guanine",                   0b0110),
        ('Y', "DNA Cytosine or Thymine",                   0b1010),
        ('K', "DNA Guanine or Thymine",                    0b1100),
        ('V', "DNA Adenine, Cytosine or Guanine",          0b0111),
        ('H', "DNA Adenine, Cytosine or Thymine",          0b1011),
        ('D', "DNA Adenine, Guanine or Thymine",           0b1101),
        ('B', "DNA Cytosine, Guanine or Thymine",          0b1110),
        ('N', "DNA Adenine, Cytosine, Guanine or Thymine", 0b1111)]
    var = Symbol("DNA_", char != '-' ? char : "Gap")
    @eval begin
        @doc $(doc) const $(var) = reinterpret(DNA, $(bits))
        char_to_dna[$(Int(char)+1)] = char_to_dna[$(Int(lowercase(char))+1)] = $(bits)
        dna_to_char[$(Int(bits)+1)] = $(char)
    end
end

@eval alphabet(::Type{DNA}) = $(tuple([reinterpret(DNA, x)
                                                 for x in 0b0000:0b1111]...))

const ACGT = (DNA_A, DNA_C, DNA_G, DNA_T)
const ACGTN = (DNA_A, DNA_C, DNA_G, DNA_T, DNA_N)

# RNA

# lookup table for characters
const char_to_rna = [0x80 for _ in 0x00:0xff]
const rna_to_char = Vector{Char}(16)

for (char, doc, dna) in [
        ('-', "RNA Gap",                                  DNA_Gap),
        ('A', "RNA Adenine",                              DNA_A  ),
        ('C', "RNA Cytosine",                             DNA_C  ),
        ('G', "RNA Guanine",                              DNA_G  ),
        ('U', "RNA Uracil",                               DNA_T  ),
        ('M', "RNA Adenine or Cytosine",                  DNA_M  ),
        ('R', "RNA Adenine or Guanine",                   DNA_R  ),
        ('W', "RNA Adenine or Uracil",                    DNA_W  ),
        ('S', "RNA Cytosine or Guanine",                  DNA_S  ),
        ('Y', "RNA Cytosine or Uracil",                   DNA_Y  ),
        ('K', "RNA Guanine or Uracil",                    DNA_K  ),
        ('V', "RNA Adenine, Cytosine or Guanine",         DNA_V  ),
        ('H', "RNA Adenine, Cytosine or Uracil",          DNA_H  ),
        ('D', "RNA Adenine, Guanine or Uracil",           DNA_D  ),
        ('B', "RNA Cytosine, Guanine or Uracil",          DNA_B  ),
        ('N', "RNA Adenine, Cytosine, Guanine or Uracil", DNA_N  )]
    var = Symbol("RNA_", char != '-' ? char : "Gap")
    @eval begin
        @doc $(doc) const $(var) = reinterpret(RNA, $(dna))
        char_to_rna[$(Int(char)+1)] = char_to_rna[$(Int(lowercase(char)+1))] = reinterpret(UInt8, $(dna))
        rna_to_char[$(Int(dna)+1)] = $(char)
    end
end

@eval alphabet(::Type{RNA}) = $(tuple([reinterpret(RNA, x)
                                                 for x in 0b0000:0b1111]...))

const ACGU = (RNA_A, RNA_C, RNA_G, RNA_U)
const ACGUN = (RNA_A, RNA_C, RNA_G, RNA_U, RNA_N)

# Print functions
# ---------------

function Base.convert(::Type{DNA}, c::Char)
    if c > '\xff'
        throw(InexactError())
    end
    @inbounds dna = char_to_dna[Int(c) + 1]
    if !isvalid(DNA, dna)
        throw(InexactError())
    end
    return reinterpret(DNA, dna)
end

function Base.convert(::Type{RNA}, c::Char)
    if c > '\xff'
        throw(InexactError())
    end
    @inbounds rna = char_to_rna[Int(c) + 1]
    if !isvalid(RNA, rna)
        throw(InexactError())
    end
    return reinterpret(RNA, rna)
end

function Base.convert(::Type{Char}, nt::DNA)
    return dna_to_char[convert(UInt8, nt) + 1]
end

function Base.convert(::Type{Char}, nt::RNA)
    return rna_to_char[convert(UInt8, nt) + 1]
end

function Base.show(io::IO, nt::DNA)
    if isvalid(nt)
        if nt == DNA_Gap
            write(io, "DNA_Gap")
        else
            write(io, "DNA_", Char(nt))
        end
    else
        write(io, "Invalid DNA")
    end
    return
end

function Base.show(io::IO, nt::RNA)
    if isvalid(nt)
        if nt == RNA_Gap
            write(io, "RNA_Gap")
        else
            write(io, "RNA_", Char(nt))
        end
    else
        write(io, "Invalid RNA")
    end
    return
end

function Base.print(io::IO, nt::NucleicAcid)
    if !isvalid(nt)
        throw(ArgumentError("nucleic acid is invalid"))
    end
    write(io, Char(nt))
    return
end


@render_program LineProgram
@vao LineVao

LineUploader() = Uploader{LineProgram}()
shaders(::Type{LineProgram}) = line_shaders()

function set_entity_uniforms_func(render_program::LineProgram, system::System)
    prog     = render_program.program
    comp(T)  = component(system, T)
    modelmat = comp(ModelMat)
    line     = comp(Line)
    return e -> begin
        gluniform(prog, :modelmat, modelmat[e].modelmat)
        gluniform(prog, :thickness, line[e].thickness)
        gluniform(prog, :MiterLimit, line[e].miter)
    end
end

struct LineRenderer <: AbstractRenderSystem end

function Overseer.requested_components(::LineRenderer)
    return (LineVao, LineProgram, ModelMat, Material, PointLight, Spatial, Camera3D,
            IOTarget, LineOptions)
end

function Overseer.update(::LineRenderer, m::AbstractLedger)
    fbo  = singleton(m, IOTarget)
    prog = singleton(m, LineProgram)
    idc  = m[Selectable]
    bind(fbo)
    draw(fbo)
    glDisable(GL_BLEND)
    glEnable(GL_DEPTH_TEST)
    glDepthFunc(GL_LEQUAL)

    bind(prog)
    light, ucolor, spat, modelmat, cam = m[PointLight], m[UniformColor], m[Spatial],
                                         m[ModelMat], m[Camera3D]
    for e in @entities_in(light && ucolor && spat)
        gluniform(prog, light[e], ucolor[e], spat[e])
    end
    for e in @entities_in(spat && cam)
        gluniform(prog, spat[e], cam[e])
    end
    gluniform(prog, :Viewport, Vec2f0(size(m[IOTarget][1])))
    vao, modelmat, line, vis = m[LineVao], m[ModelMat], m[LineOptions], m[Visible]
    for e in @entities_in(vao && modelmat && line)
        evao = vao[e]
        e_line = line[e]
        if vis[e].visible
            gluniform(prog, :modelmat, modelmat[e].modelmat)
            gluniform(prog, :thickness, e_line.thickness)
            gluniform(prog, :MiterLimit, e_line.miter)
            if e in idc
                gluniform(prog, :object_id_color, idc[e].color)
            else
                gluniform(prog, :object_id_color, RGBf0(1.0, 1.0, 1.0))
            end
            GLA.bind(evao)
            GLA.draw(evao)
        end
    end
end


function load(path::String, sample::Union{EzXML.Node,Nothing}; transform::Function=x->asinh(x/250), kwargs...)

	#######################################
    params, data = loadFCS(path; kwargs...)
	params = sample === nothing ? getMetaData(params) : findchannels(sample)

	###################################### data with human readable channel names
	channels = dropmissing(params,"E")
	transform!(channels, ["N","S"] => ByRow((N,S)-> ~ismissing(S) ? S != "" ? S : N : N) => "name")
	data = DataFrame(data,channels.name)

	###################################### biexponential transformation
	@. data = transform(data)

    ################################ load metadata from workspace
	groups = loadGroups(data,sample)
	gating = gatingGraph(sample; transform=transform,
		channelMap=Dict( param.N => ismissing(param.S) ? param.N : param.S for param ∈ eachrow(params) )
	)

	labels = gate(data,gating)
	return data,labels,groups,gating
end

function load(path::String; files::String=joinpath(dirname(path),"*.fcs"), transform::Function=x->asinh(x/250), cols::Symbol=:setequal, kwargs...)

	@assert( isfile(path), "no such file: $path")
	@assert( length(glob(files)) ≠ 0, "no FCS files found using pattern: $files")

	try
		workspace = root(readxml(path))
	catch error
		throw(LoadError(path,error.line,error))
	end

	workspace = root(readxml(path))
	datasets = map( dataset -> basename(dataset["uri"]), findall("//DataSet",workspace) )
	@assert( length(datasets) == length(unique(datasets)), "FCS files under a workspace must have unique names" )

	data,labels,groups = DataFrame(),DataFrame(),DataFrame()
	gatings = Dict()

	for path ∈ glob(files)
		@info "Loading $path"
		sample = findsample(path,workspace)

		fcs,label,group,gating = load(path,sample;transform=transform,kwargs...)

		append!(data,fcs,cols=cols)
		append!(labels,label,cols=:union)
		append!(groups,group,cols=:union)
		gatings[path] = gating
	end

	map( name->replace!(labels[!,name],missing=>false), names(labels) )
	map( name->replace!(groups[!,name],missing=>false), names(groups) )

	disallowmissing!(labels)
	disallowmissing!(groups)

	transform!( labels, AsTable(filter(x->x≠"Unlabelled",names(labels))) => ByRow(x->~any(x)) => "Unlabelled" )
	transform!( groups, AsTable(filter(x->x≠"Ungrouped",names(groups))) =>  ByRow(x->~any(x)) => "Ungrouped" )

	return data,labels,groups,gatings
end

function findsample(uri::String,workspace::EzXML.Node)
	uri = escapeuri(uri)

	for code ∈ [("%2F"=>"/"), ("%5C"=>"\\"), ("%3A"=>":"), ("%2C"=>",")]
		uri = replace(uri, code)
	end

	sample = findfirst("//DataSet[contains(@uri,'$(basename(uri))')]",workspace)
	sample === nothing && @warn("""no metadata found""")
	return sample
end

function findchannels(sample::EzXML.Node)

	params = DataFrame( "keyword" => findall("..//Keywords/Keyword[starts-with(@name,'\$P') and contains(@name,'N')]", sample))
	keys = ["G","R","V","S","B","N","E","AR"]

	for key ∈ keys
		transform!(params, "keyword" => ByRow( x->getparam(chop(x["name"],head=0,tail=1)*key, sample)) => key)
	end

	transform!(params, "keyword" => ByRow( x->parse(Int,chop(x["name"],head=2,tail=1))) => "index")
	sort!(params,"index")

	transform!(params,["N","S"]=>ByRow((N,S)-> ~ismissing(S) ? S : stripname(N,params.N))=>"S")
	return select!(params,keys)
end

function stripname(name::String,names::AbstractArray{<:String})
    names = filter(x->occursin(x,name)&(x≠name), names)
    return isempty(names) ? missing : first(names)
end

function getparam(param::String,sample::EzXML.Node)
	keyword = findfirst("""..//Keywords/Keyword[@name='$param']""", sample)
	return keyword !== nothing ? keyword["value"] != "" ? keyword["value"] : missing : missing
end


struct Unit
    CategoryHead::String
    Unit::String
    Icon::String # url
    NATOSymbol::String # url
    HP::Float64
    Org::Float64
    Recov::Float64
    Suppr::Float64
    Weight::Float64
    Supply::Float64
    Width::Float64
    SpecialEffects::String
    Manpower::Int64
    Training::Int64
    Equip::String # TODO: detailed parse
    Equipment_Modifier::String
    Category::String
    Speed::Float64 # 8.0/10.0/12.0 -> 8.0
    SoftAtk::Float64
    HardAtk::Float64
    AirAtk::Float64
    Defense::Float64
    Breakthr::Float64
    Armor::Float64 # 10/15/20 -> 10
    Pierce::Float64
    Hardness::Float64 # 64% -> 0.64
    FuelCapacity::Float64
    FuelUsage::Float64
    ProdCost::Float64
    UnitTail::String
end

Base.@kwdef struct DivisionTemplate # kwdef make it possible to use keyword specify parameter
    unitList::Array{Unit,1}
    HP::Float64
    Org::Float64
    Recov::Float64
    Suppr::Float64
    Weight::Float64
    Supply::Float64
    Width::Float64
    Manpower::Int64
    Training::Int64
    Speed::Float64
    SoftAtk::Float64
    HardAtk::Float64
    AirAtk::Float64
    Defense::Float64
    Breakthr::Float64
    Armor::Float64
    Pierce::Float64
    Hardness::Float64
    FuelCapacity::Float64
    FuelUsage::Float64
    ProdCost::Float64
end

mutable struct Division
    T::DivisionTemplate # template, max HP, max Org, etc...
    HP::Float64
    Org::Float64
end


module Highway

using JLD

import Base:
        show

export
        Vehicle, Highway1D, Highway2D,
        Empty_Cell!, Obstacle!, Change_Vehicle!, Insert_Vehicle!,  Vehicle_Behind, Vehicle_Ahead,
        Pos_next, Pos_right, Pos_left, Pos_left9,
        Merge!, Merge_Left_Right!, Merge_Right_Left!, safe_distance, Ramp!,
        AccelerationNoise, DecelerationMove, AccelerationNoiseS1, AccelerationNoiseS2,
        Measure_Fluxes!, Measure_Speeds!, add_Fluxes!, add_Speeds!, Measure_Densities!, Measure!, Measure_s!,
        Sense1, Sense2, Sections1_CR, Sections2_CR,
        S1_ramp, S1_ramp_ampli, S1_ramp_local, P_ramp_S1, P_ampli_S1, P_local_S1,
        S2_ramp, S2_ramp_ampli, S2_ramp_local, P_ramp_S2, P_ampli_S2, P_local_S2

include("types.jl")
include("aux-functions.jl")
include("AC-functions.jl")
include("merging-ramps.jl")
include("measurements.jl")
include("senses.jl")

end # module Highway


#################################################################################
#
#   FUNCTIONS FOR ADDING / REMOVING SITES IN LATTICES AND UNITCELLS
#
#   STRUCTURE OF THE FILE:
#   - removing single sites
#       - from unitcells
#       - from lattices
#   - removing multiple sites
#       - from unitcells
#       - from lattices
#   - removing disconnected sites
#       - from unitcells
#       - from lattices
#
#   - adding single sites
#       - to unitcells
#       - to lattices
#   - adding multiple sites
#       - to unitcells
#       - to lattices
#
################################################################################

################################################################################
#
#   REMOVING SINGLE SITES
#       - from unitcells
#       - from lattices
#
################################################################################

# Remove a single site from a unitcell (and return that)
function removeSite!(
        unitcell :: U,
        index    :: Integer
    ) :: S where {D,N,LS,LB, S<:AbstractSite{LS,D}, B<:AbstractBond{LB,N}, U<:AbstractUnitcell{S,B}}

    # store the site to be removed
    s = sites(unitcell)[index]

    # remove the site from the list and pass the new list again to the unitcell
    sites!(unitcell, deleteat!(sites(unitcell), index))

    # get all bonds that are not connected to the removed site
    bond_list = filter(b -> to(b) != index && from(b) != index, bonds(unitcell))
    # relabeling of bond indices
    for b in bond_list
        # set the new indices
        from!(b, from(b)>index ? from(b)-1 : from(b))
        to!(b,   to(b)>index   ? to(b)-1   : to(b))
    end
    # set the bond list in the unitcell object
    bonds!(unitcell, bond_list)
    # return the removed site
    return s
end

# Remove a single site from a lattice (and return that)
function removeSite!(
        lattice :: L,
        index   :: Integer
    ) :: SL where {D,N,LS,LB, S<:AbstractSite{LS,D}, B<:AbstractBond{LB,N}, U<:AbstractUnitcell{S,B},
        DL,NL,LSL,LBL,SL<:AbstractSite{LSL,DL}, BL<:AbstractBond{LBL,NL}, L<:AbstractLattice{SL,BL,U}}

    # store the site to be removed
    s = sites(lattice)[index]

    # remove the site from the list and pass the new list again to the lattice
    sites!(lattice, deleteat!(sites(lattice), index))

    # get all bonds that are not connected to the removed site
    bond_list = filter(b -> to(b) != index && from(b) != index, bonds(lattice))
    # relabeling of bond indices
    for b in bond_list
        # set the new indices
        from!(b, from(b)>index ? from(b)-1 : from(b))
        to!(b,   to(b)>index   ? to(b)-1   : to(b))
    end
    # set the bond list in the lattice object
    bonds!(lattice, bond_list)
    # return the removed site
    return s
end

# export the function
export removeSite!

################################################################################
#
#   REMOVING MULTIPLE SITES
#       - from unitcells
#       - from lattices
#
################################################################################

# Remove multiple sites from a unitcell (and return them)
function removeSite!(
        unitcell :: U,
        index    :: Integer...
    ) :: Vector{S} where {D,N,LS,LB, S<:AbstractSite{LS,D}, B<:AbstractBond{LB,N}, U<:AbstractUnitcell{S,B}}

    # return the respective function
    return removeSite!(unitcell, unique(index))
end

# Remove multiple sites from a unitcell (and return them)
function removeSite!(
        unitcell :: U,
        index    :: Vector{<:Integer}
    ) :: Vector{S} where {D,N,LS,LB, S<:AbstractSite{LS,D}, B<:AbstractBond{LB,N}, U<:AbstractUnitcell{S,B}}

    # get the list of sites
    sites_to_return = S[site(unitcell,i) for i in index]

    # get all sites of the unitcell
    site_list = sites(unitcell)
    # build a index list
    indices_new = collect(1:length(site_list))
    index_gets_removed = Bool[false for i in 1:numSites(unitcell)]
    # fill the new index list at positions of removed sites
    for i in index
        indices_new[i] = -1
        index_gets_removed[i] = true
    end
    # fill the new list successively from smallest to largest
    if indices_new[1] == -1
        indices_new[1] = 0
    end
    for i in 2:length(indices_new)
        if indices_new[i] == -1
            indices_new[i] = indices_new[i-1]
        else
            indices_new[i] = indices_new[i-1] + 1
        end
    end
    # get a new site list
    site_list = S[site_list[i] for i in 1:length(site_list) if !index_gets_removed[i]]
    # register new list of sites within the unitcell object
    sites!(unitcell, site_list)

    # get all bonds that are not connected to any removed site
    bond_list = filter(b -> !(index_gets_removed[to(b)] || index_gets_removed[from(b)]), bonds(unitcell))
    # relabeling of bond indices
    for b in bond_list
        # set the new indices
        from!(b, indices_new[from(b)])
        to!(b,   indices_new[to(b)])
    end
    # set the bond list in the unitcell object
    bonds!(unitcell, bond_list)

    # return the removed sites
    return sites_to_return
end

# Remove multiple sites from a lattice (and return them)
function removeSite!(
        lattice :: L,
        index   :: Integer ...
    ) :: Vector{SL} where {D,N,LS,LB, S<:AbstractSite{LS,D}, B<:AbstractBond{LB,N}, U<:AbstractUnitcell{S,B},
        DL,NL,LSL,LBL,SL<:AbstractSite{LSL,DL}, BL<:AbstractBond{LBL,NL}, L<:AbstractLattice{SL,BL,U}}

    # call the respective funtion
    return removeSite!(lattice, unique(index))
end

# Remove multiple sites from a lattice (and return them)
function removeSite!(
        lattice :: L,
        index   :: Vector{<:Integer}
    ) :: Vector{SL} where {D,N,LS,LB, S<:AbstractSite{LS,D}, B<:AbstractBond{LB,N}, U<:AbstractUnitcell{S,B},
        DL,NL,LSL,LBL,SL<:AbstractSite{LSL,DL}, BL<:AbstractBond{LBL,NL}, L<:AbstractLattice{SL,BL,U}}

    # get the list of sites
    sites_to_return = SL[site(lattice,i) for i in index]

    # get all sites of the lattice
    site_list = sites(lattice)
    # build a index list
    indices_new = collect(1:length(site_list))
    index_gets_removed = Bool[false for i in 1:numSites(lattice)]
    # fill the new index list at positions of removed sites
    for i in index
        indices_new[i] = -1
        index_gets_removed[i] = true
    end
    # fill the new list successively from smallest to largest
    if indices_new[1] == -1
        indices_new[1] = 0
    end
    for i in 2:length(indices_new)
        if indices_new[i] == -1
            indices_new[i] = indices_new[i-1]
        else
            indices_new[i] = indices_new[i-1] + 1
        end
    end
    # get a new site list
    site_list = SL[site_list[i] for i in 1:length(site_list) if !index_gets_removed[i]]
    # register new list of sites within the lattice object
    sites!(lattice, site_list)

    # get all bonds that are not connected to any removed site
    bond_list = filter(b -> !(index_gets_removed[to(b)] || index_gets_removed[from(b)]), bonds(lattice))
    # relabeling of bond indices
    for b in bond_list
        # set the new indices
        from!(b, indices_new[from(b)])
        to!(b,   indices_new[to(b)])
    end
    # set the bond list in the lattice object
    bonds!(lattice, bond_list)

    # return the removed sites
    return sites_to_return
end

################################################################################
#
#   REMOVING DISCONNECTED SITES
#       - from unitcells
#       - from lattices
#
################################################################################

# Remove multiple disconnected sites (from site with index) from a unitcell (and return them)
function removeDisconnectedSites!(
        unitcell :: U,
        index    :: Integer = 1
    ) :: Vector{S} where {D,N,LS,LB, S<:AbstractSite{LS,D}, B<:AbstractBond{LB,N}, U<:AbstractUnitcell{S,B}}

    #######################################
    # label all sites by Hoshen Kopelman
    #######################################

    # label variables
    current_label = 2
    site_labels = zeros(Int64, numSites(unitcell)) .- 1
    site_labels[index] = 1

    # the list of real labels
    real_labels = zeros(Int64, numSites(unitcell)) .- 1
    function reallabel(l :: Int64) :: Int64
        if real_labels[l] == -1
            return l
        else
            return reallabel(real_labels[l])
        end
    end

    # the list of bonds (organized)
    bond_list = organizedBondsFrom(unitcell)

    # iterate over all sites
    for s in 1:numSites(unitcell)
        # skip the preset one
        if s == index
            continue
        end
        # check neighbors
        for nb in bond_list[s]
            # check if a label is already set
            if site_labels[to(nb)] > 0
                # set the current label
                if site_labels[s] > 0 && reallabel(site_labels[s])==reallabel(site_labels[to(nb)])
                    # do nothing
                elseif site_labels[s] > 0
                    # make new connection
                    real_labels[max(site_labels[s], site_labels[to(nb)])] = min(site_labels[s], site_labels[to(nb)])
                    # label accordingly
                    site_labels[s] = min(site_labels[s], site_labels[to(nb)])
                else
                    # just use the label
                    site_labels[s] = reallabel(site_labels[to(nb)])
                end
            end
        end
        # if still no label, create new one
        if site_labels[s] < 0
            site_labels[s] = current_label
            current_label = current_label + 1
        end
    end

    # replace all labels by their real counterpart
    site_labels = map(s -> reallabel(s), site_labels)

    # remove all labels that are not 1
    sites_to_remove = [i for i in 1:numSites(unitcell) if site_labels[i]!=1]
    if length(sites_to_remove) == 0
        return S[]
    else
        return removeSite!(unitcell, sites_to_remove)
    end
end

# Remove multiple disconnected sites (from site with index) from a lattice (and return them)
function removeDisconnectedSites!(
        lattice :: L,
        index   :: Integer = 1
    ) :: Vector{SL} where {D,N,LS,LB, S<:AbstractSite{LS,D}, B<:AbstractBond{LB,N}, U<:AbstractUnitcell{S,B},
        DL,NL,LSL,LBL,SL<:AbstractSite{LSL,DL}, BL<:AbstractBond{LBL,NL}, L<:AbstractLattice{SL,BL,U}}

    #######################################
    # label all sites by Hoshen Kopelman
    #######################################

    # label variables
    current_label = 2
    site_labels = zeros(Int64, numSites(lattice)) .- 1
    site_labels[index] = 1

    # the list of real labels
    real_labels = zeros(Int64, numSites(lattice)) .- 1
    function reallabel(l :: Int64) :: Int64
        if real_labels[l] == -1
            return l
        else
            return reallabel(real_labels[l])
        end
    end

    # the list of bonds (organized)
    bond_list = organizedBondsFrom(lattice)

    # iterate over all sites
    for s in 1:numSites(lattice)
        # skip the preset one
        if s == index
            continue
        end
        # check neighbors
        for nb in bond_list[s]
            # check if a label is already set
            if site_labels[to(nb)] > 0
                # set the current label
                if site_labels[s] > 0 && reallabel(site_labels[s])==reallabel(site_labels[to(nb)])
                    # do nothing
                elseif site_labels[s] > 0
                    # make new connection
                    real_labels[max(site_labels[s], site_labels[to(nb)])] = min(site_labels[s], site_labels[to(nb)])
                    # label accordingly
                    site_labels[s] = min(site_labels[s], site_labels[to(nb)])
                else
                    # just use the label
                    site_labels[s] = reallabel(site_labels[to(nb)])
                end
            end
        end
        # if still no label, create new one
        if site_labels[s] < 0
            site_labels[s] = current_label
            current_label = current_label + 1
        end
    end

    # replace all labels by their real counterpart
    site_labels = map(s -> reallabel(s), site_labels)

    # remove all labels that are not 1
    sites_to_remove = [i for i in 1:numSites(lattice) if site_labels[i]!=1]
    if length(sites_to_remove) == 0
        return S[]
    else
        return removeSite!(lattice, sites_to_remove)
    end
end

# export functions
export removeDisconnectedSites!

################################################################################
#
#   ADDING SINGLE SITES
#       - to unitcells
#       - to lattices
#
################################################################################

# Add a single site to a unitcell and return its index in the list
function addSite!(
            unitcell :: U,
            position :: Vector{<:Real},
            label    :: L = getDefaultLabel(L)
        ) :: Int64 where {L,D,B,S<:AbstractSite{L,D},U<:AbstractUnitcell{S,B}}

    # create a new site
    site = newSite(S, position, label)

    # obtain the list of sites in the unitcell
    site_list = sites(unitcell)
    # push to the list of sites
    push!(site_list, site)
    # set the list of sites in the unitcell
    sites!(unitcell, site_list)

    # return the new site
    return length(site_list)
end

# Add a single site to a lattice and return its index in the list
function addSite!(
            lattice  :: LA,
            position :: Vector{<:Real},
            label    :: L = getDefaultLabel(L)
        ) :: Int64 where {L,D,B,S<:AbstractSite{L,D},U,LA<:AbstractLattice{S,B,U}}

    # create a new site
    site = newSite(S, position, label)

    # obtain the list of sites in the lattice
    site_list = sites(lattice)
    # push to the list of sites
    push!(site_list, site)
    # set the list of sites in the lattice
    sites!(lattice, site_list)

    # return the new site
    return length(site_list)
end

# export the function
export addSite!


module BlasIntrinsics

using Base:llvmcall

# https://software.intel.com/sites/landingpage/IntrinsicsGuide/#techs=FMA

# Plain baseline registers
const half2   = NTuple{2, VecElement{Float16}}
const half4   = NTuple{4, VecElement{Float16}}
#
const float2  = NTuple{2, VecElement{Float32}}
const float4  = NTuple{4, VecElement{Float32}}
const float8  = NTuple{8, VecElement{Float32}}
const float16 = NTuple{16,VecElement{Float32}}
#
const double2 = NTuple{2, VecElement{Float64}}
const double4 = NTuple{4, VecElement{Float64}}
const double8 = NTuple{8, VecElement{Float64}}

@inline m256dfma(a,b,c) = ccall("llvm.fma.v4f64", llvmcall, double4, (double4, double4, double4), a, b, c)
@inline m256fma(a,b,c ) = ccall("llvm.fma.v8f32", llvmcall, float8, (float8, float8, float8), a, b, c)

@inline m512dfma(a,b,c) = ccall("llvm.fma.v8f64", llvmcall, double8, (double8, double8, double8), a, b, c)
@inline m512fma(a,b,c ) = ccall("llvm.fma.v16f32", llvmcall, float16, (float16, float16, float16), a, b, c)

@inline prefetch(a, i1, i2, i3) = ccall("llvm.prefetch", llvmcall, Cvoid, (Ptr{Cchar}, Int32, Int32, Int32), a, i1, i2, i3)

@inline prefetchT0(a) = mmprefetch(a, Int32(0), Int32(3), Int(1))
@inline prefetchT1(a) = mmprefetch(a, Int32(0), Int32(2), Int(1))
@inline prefetchT2(a) = mmprefetch(a, Int32(0), Int32(1), Int(1))

@inline function mm_mul(x::T, y::T) where T <:AbstractFloat
   return x * y
end

@inline function mm_mul(x::float4, y::float4)
  (VecElement(x[1].value * y[1].value),
   VecElement(x[2].value * y[2].value),
   VecElement(x[3].value * y[3].value),
   VecElement(x[4].value * y[4].value))
end

@inline function mm_mul(x::double2, y::double2)
  (VecElement(x[1].value * y[1].value),
   VecElement(x[2].value * y[2].value))
end

@inline function mm_mul(x::float8, y::float8)
  (VecElement(x[1].value * y[1].value),
   VecElement(x[2].value * y[2].value),
   VecElement(x[3].value * y[3].value),
   VecElement(x[4].value * y[4].value),
   VecElement(x[5].value * y[5].value),
   VecElement(x[6].value * y[6].value),
   VecElement(x[7].value * y[7].value),
   VecElement(x[8].value * y[8].value))
end

@inline function mm_mul(x::double4, y::double4)
  (VecElement(x[1].value * y[1].value),
   VecElement(x[2].value * y[2].value),
   VecElement(x[3].value * y[3].value),
   VecElement(x[4].value * y[4].value))
end

@inline function mm_add(x::T, y::T) where T <:AbstractFloat
   return x + y
end

@inline function mm_add(x::float4, y::float4)
  (VecElement(x[1].value + y[1].value),
   VecElement(x[2].value + y[2].value),
   VecElement(x[3].value + y[3].value),
   VecElement(x[4].value + y[4].value))
end

@inline function mm_add(x::double2, y::double2)
  (VecElement(x[1].value + y[1].value),
   VecElement(x[2].value + y[2].value))
end

@inline function mm_add(x::float8, y::float8)
  (VecElement(x[1].value + y[1].value),
   VecElement(x[2].value + y[2].value),
   VecElement(x[3].value + y[3].value),
   VecElement(x[4].value + y[4].value),
   VecElement(x[5].value + y[5].value),
   VecElement(x[6].value + y[6].value),
   VecElement(x[7].value + y[7].value),
   VecElement(x[8].value + y[8].value))
end

@inline function mm_add(x::double4, y::double4)
  (VecElement(x[1].value + y[1].value),
   VecElement(x[2].value + y[2].value),
   VecElement(x[3].value + y[3].value),
   VecElement(x[4].value + y[4].value))
end

@inline function mm_sub(x::T, y::T) where T <:AbstractFloat
   return x - y
end

@inline function mm_sub(x::float4, y::float4)
  (VecElement(x[1].value - y[1].value),
   VecElement(x[2].value - y[2].value),
   VecElement(x[3].value - y[3].value),
   VecElement(x[4].value - y[4].value))
end

@inline function mm_sub(x::double2, y::double2)
  (VecElement(x[1].value - y[1].value),
   VecElement(x[2].value - y[2].value))
end

@inline function mm_sub(x::float8, y::float8)
  (VecElement(x[1].value - y[1].value),
   VecElement(x[2].value - y[2].value),
   VecElement(x[3].value - y[3].value),
   VecElement(x[4].value - y[4].value),
   VecElement(x[5].value - y[5].value),
   VecElement(x[6].value - y[6].value),
   VecElement(x[7].value - y[7].value),
   VecElement(x[8].value - y[8].value))
end

@inline function mm_sub(x::double4, y::double4)
  (VecElement(x[1].value - y[1].value),
   VecElement(x[2].value - y[2].value),
   VecElement(x[3].value - y[3].value),
   VecElement(x[4].value - y[4].value))
end

@inline function mm_mad(a::T, x::T, y::T) where T <:AbstractFloat
   return a * x + y
end

@inline function mm_mad(a::T, x::float4, y::float4) where T <: AbstractFloat
  (VecElement(a * x[1].value + y[1].value),
   VecElement(a * x[2].value + y[2].value),
   VecElement(a * x[3].value + y[3].value),
   VecElement(a * x[4].value + y[4].value))
end

@inline function mm_mad(a::T, x::double2, y::double2) where T <: AbstractFloat
  (VecElement(a * x[1].value + y[1].value),
   VecElement(a * x[2].value + y[2].value))
end

@inline function mm_mad(a::T, x::float8, y::float8) where T <: AbstractFloat
   z = float8((a, a, a, a, a, a, a, a))
   return m256fma(z,x,y)
end

@inline function mm_mad(a::T, x::double4, y::double4) where T <: AbstractFloat
   z = double4((a, a, a, a))
   return m256dfma(z,x,y)
end

end #QJuliaIntrinsics


using CUDArt, PyPlot

CUDArt.init([0])

md=CuModule("vadd.ptx",false)
kernel=CuFunction(md,"vadd")

function vadd(A::CudaArray,B::CudaArray,C::CudaArray)
    nblocks=round(Int,ceil(length(B)/1024))
    launch(kernel,nblocks,1024,(length(A),A,B,C))
end

N=2000
A=CudaArray(rand(N,N))
B=CudaArray(rand(N,N))
C=CudaArray(zeros(N,N))

M=5000
tm=zeros(M)
hA=to_host(A);
hB=to_host(B);
hC=to_host(C);

GPU=true
tic()
for i in 1:M
    if GPU vadd(A,B,C)
    else
        hC=hA+hB
    end
end
if GPU CUDArt.device_synchronize() end
#to_host(C)
avtime=toc()/M;
println("average time per vadd = $avtime")


module Yolk

using Mixtape
import Mixtape: CompilationContext, allow, optimize!
using MacroTools: @capture, postwalk, rmlines, unblock
using CodeInfoTools
using CompilerPluginTools: inline_const!
using Core.Compiler: Const, is_pure_intrinsic_infer, intrinsic_nothrow, anymap, quoted, cfg_simplify!, compact!, adce_pass!

using Metatheory
using Metatheory.Library
using Metatheory.EGraphs

@metatheory_init()

include("methodtheory.jl")

struct YolkOptimizer <: CompilationContext
    theory::Vector{Rule}
    block_size_limit::Int
    opt::Bool
    YolkOptimizer(t) = new(t, 2, true)
    YolkOptimizer(t, b) = new(t, 2, b)
    YolkOptimizer() = new(Rule[])
end
allow(::YolkOptimizer, m::Module, args...) = true

shrink(ex::Expr, t) = t
shrink(ex::T, t) where T = Const(ex)

local_inline(g, stmts) = g
local_inline(g::GlobalRef, stmts) = CodeInfoTools.resolve(g)
function local_inline(g::Core.Compiler.SSAValue, stmts)
    g.id <= length(stmts) || return g
    local_inline(Core.Compiler.getindex(stmts, g.id)[:inst], stmts)
end
local_inline(ex::Expr, stmts) = Expr(ex.head, map(a -> local_inline(a, stmts), ex.args)...)

function extract!(ctx, ir)
    theory = ctx.theory
    for i in 1 : ctx.block_size_limit : length(ir.stmts)
        stmt = ir.stmts[i][:inst]
        type = ir.stmts[i][:type]
        if stmt isa Expr && stmt.head == :call
            if allow(ctx, CodeInfoTools.resolve(stmt.args[1]))
                r = local_inline(stmt, ir.stmts)
                G = EGraph(r)
                saturate!(G, theory)
                ex = Metatheory.EGraphs.extract!(G, astsize)
                if !(ex == r)
                    Core.Compiler.setindex!(ir.stmts[i], ex, :inst)
                    Core.Compiler.setindex!(ir.stmts[i], shrink(ex, type), :type)
                end
            end
        end
    end
    return ir
end

function optimize!(ctx::YolkOptimizer, b)
    ir = get_ir(b)
    if !ctx.opt
        ir = julia_passes!(ir, b.sv.src, b.sv)
    else
        ir = extract!(ctx, ir)
        ir = inline_const!(ir)
        ir = compact!(ir, true)
        ir = cfg_simplify!(ir)
        ir = julia_passes!(ir, b.sv.src, b.sv)
    end
    return ir
end

function opt(fn, tt::Type{T};
        emit_callable = false,
        ctx = YolkOptimizer(),
        opt = true) where T <: Tuple
    emit_callable && return jit(fn, tt; ctx = ctx, opt = opt)
    return emit(fn, tt; ctx = ctx, opt = opt)
end

export opt, YolkOptimizer, @methodtheory

end # module


module NIfTI

using CodecZlib, Mmap, MappedArrays, TranscodingStreams

import Base.getindex, Base.size, Base.ndims, Base.length, Base.write, Base64
export NIVolume, niread, niwrite, voxel_size, time_step, vox, getaffine, setaffine

include("parsers.jl")
include("extensions.jl")
include("volume.jl")
include("headers.jl")

"""
    NIVolume{T<:Number,N,R} <: AbstractArray{T,N}
An `N`-dimensional NIfTI volume, with raw data of type
`R`. Note that if `R <: Number`, it will be converted to `Float32`. Additionally, the header is automatically
updated to be consistent with the raw volume.

# Members
- `header`: a `NIfTI1Header`
- `extensions`: a Vector of `NIfTIExtension`s
- `raw`: Raw data of type `R`
"""
struct NIVolume{T<:Number,N,R} <: AbstractArray{T,N}
    header::NIfTI1Header
    extensions::Vector{NIfTIExtension}
    raw::R
end
NIVolume(header::NIfTI1Header, extensions::Vector{NIfTIExtension}, raw::R) where {R}=
    niupdate(new(header, extensions, raw))

NIVolume(header::NIfTI1Header, extensions::Vector{NIfTIExtension}, raw::AbstractArray{T,N}) where {T<:Number,N} =
    NIVolume{typeof(one(T)*1f0+1f0),N,typeof(raw)}(header, extensions, raw)
NIVolume(header::NIfTI1Header, raw::AbstractArray{T,N}) where {T<:Number,N} =
    NIVolume{typeof(one(T)*1f0+1f0),N,typeof(raw)}(header, NIfTIExtension[], raw)
NIVolume(header::NIfTI1Header, extensions::Vector{NIfTIExtension}, raw::AbstractArray{Bool,N}) where {N} =
    NIVolume{Bool,N,typeof(raw)}(header, extensions, raw)
NIVolume(header::NIfTI1Header, raw::AbstractArray{Bool,N}) where {N} =
    NIVolume{Bool,N,typeof(raw)}(header, NIfTIExtension[], raw)

header(x::NIVolume) = getfield(x, :header)

include("coordinates.jl")

"""
    voxel_size(header::NIfTI1Header)

Get the voxel size **in mm** from a `NIfTI1Header`.
"""
voxel_size(header::NIfTI1Header) =
    [header.pixdim[i] * SPATIAL_UNIT_MULTIPLIERS[header.xyzt_units & Int8(3)] for i = 2:min(header.dim[1], 3)+1]

# Always in ms
"""
    time_step(header::NIfTI1Header)

Get the TR **in ms** from a `NIfTI1Header`.
"""
time_step(header::NIfTI1Header) =
    header.pixdim[5] * TIME_UNIT_MULTIPLIERS[header.xyzt_units >> 3]

function to_dim_info(dim_info::Tuple{Integer,Integer,Integer})
    if dim_info[1] > 3 || dim_info[1] < 0
        error("Invalid frequency dimension $(dim_info[1])")
    elseif dim_info[2] > 3 || dim_info[2] < 0
        error("Invalid phase dimension $(dim_info[2])")
    elseif dim_info[3] > 3 || dim_info[3] < 0
        error("Invalid slice dimension $(dim_info[3])")
    end

    return Int8(dim_info[1] | (dim_info[2] << 2) | (dim_info[3] << 4))
end

# Returns or sets dim_info as a tuple whose values are the frequency, phase, and slice dimensions
function dim_info(header::NIfTI1Header)
    return (
        header.dim_info & int8(3),
        (header.dim_info >> 2) & int8(3),
        (header.dim_info >> 4) & int8(3)
    )
end
function dim_info(header::NIfTI1Header, dim_info::Tuple{T, T, T}) where {T<:Integer}
    header.dim_info = to_dim_info(dim_info)
end

# Gets the size of a type in bits
nibitpix(t::Type) = Int16(sizeof(t)*8)
nibitpix(::Type{Bool}) = Int16(1)

# Constructor
function NIVolume(
    # Optional MRI volume; if not given, an empty volume is used
    raw::AbstractArray{T}=Int16[],
    extensions::Union{Vector{NIfTIExtension},Nothing}=nothing;

    # Fields specified as UNUSED in NIfTI1 spec
    data_type::AbstractString="", db_name::AbstractString="", extents::Integer=Int32(0),
    session_error::Integer=Int16(0), regular::Integer=Int8(0), glmax::Integer=Int32(0),
    glmin::Integer=Int16(0),

    # The frequency encoding, phase encoding and slice dimensions.
    dim_info::NTuple{3, Integer}=(0, 0, 0),
    # Describes data contained in the file; for valid values, see
    # http://nifti.nimh.nih.gov/nifti-1/documentation/nifti1fields/nifti1fields_pages/group__NIfTI1__INTENT__CODES.html
    intent_p1::Real=0f0, intent_p2::Real=0f0, intent_p3::Real=0f0,
    intent_code::Integer=Int16(0), intent_name::AbstractString="",
    # Information about which slices were acquired, in case the volume has been padded
    slice_start::Integer=Int16(0), slice_end::Integer=Int16(0), slice_code::Int8=Int8(0),
    # The size of each voxel and the time step. These are formulated in mm unless xyzt_units is
    # explicitly specified
    voxel_size::NTuple{3, Real}=(1f0, 1f0, 1f0), time_step::Real=0f0, xyzt_units::Int8=Int8(18),
    # Slope and intercept by which volume shoudl be scaled
    scl_slope::Real=1f0, scl_inter::Real=0f0,
    # These describe how data should be scaled when displayed on the screen. They are probably
    # rarely used
    cal_max::Real=0f0, cal_min::Real=0f0,
    # The amount of time taken to acquire a slice
    slice_duration::Real=0f0,
    # Indicates a non-zero start point for time axis
    toffset::Real=0f0,

    # "Any text you like"
    descrip::AbstractString="",
    # Name of auxiliary file
    aux_file::AbstractString="",

    # Parameters for Method 2. See the NIfTI spec
    qfac::Float32=0f0, quatern_b::Real=0f0, quatern_c::Real=0f0, quatern_d::Real=0f0,
    qoffset_x::Real=0f0, qoffset_y::Real=0f0, qoffset_z::Real=0f0,
    # Orientation matrix for Method 3
    orientation::Union{Matrix{Float32},Nothing}=nothing) where {T <: Number}
    local t
    if isempty(raw)
        raw = Int16[]
        t = Int16
    else
        t = T
    end

    if extensions == nothing
        extensions = NIfTIExtension[]
    end

    method2 = qfac != 0 || quatern_b != 0 || quatern_c != 0 || quatern_d != 0 || qoffset_x != 0 ||
        qoffset_y != 0 || qoffset_z != 0
    method3 = orientation != nothing

    if method2 && method3
        error("Orientation parameters for Method 2 and Method 3 are mutually exclusive")
    end

    if method3
        if size(orientation) != (3, 4)
            error("Orientation matrix must be of dimensions (3, 4)")
        end
    else
        orientation = zeros(Float32, 3, 4)
    end

    if slice_start == 0 && slice_end == 0 && dim_info[3] != 0
        slice_start = 0
        slice_end = size(raw, dim_info[3]) - 1
    end

    NIVolume(NIfTI1Header(SIZEOF_HDR1, string_tuple(data_type, 10), string_tuple(db_name, 18), extents, session_error,
        regular, to_dim_info(dim_info), to_dim_i16(size(raw)), intent_p1, intent_p2,
        intent_p3, intent_code, eltype_to_int16(t), nibitpix(t),
        slice_start, (qfac, voxel_size..., time_step, 0, 0, 0), 352,
        scl_slope, scl_inter, slice_end, slice_code,
        xyzt_units, cal_max, cal_min, slice_duration,
        toffset, glmax, glmin, string_tuple(descrip, 80), string_tuple(aux_file, 24), (method2 || method3),
        method3, quatern_b, quatern_c, quatern_d,
        qoffset_x, qoffset_y, qoffset_z, (orientation[1, :]...,),
        (orientation[2, :]...,), (orientation[3, :]...,), string_tuple(intent_name, 16), NP1_MAGIC), extensions, raw)
end

# Validates the header of a volume and updates it to match the volume's contents
function niupdate(vol::NIVolume{T}) where {T}
    vol.header.sizeof_hdr = SIZEOF_HDR1
    vol.header.dim = to_dim_i16(size(vol.raw))
    vol.header.datatype = eltype_to_int16(T)
    vol.header.bitpix = nibitpix(T)
    vol.header.vox_offset = isempty(vol.extensions) ? Int32(352) :
        Int32(mapreduce(esize, +, vol.extensions) + SIZEOF_HDR1)
    vol
end

# Avoid method ambiguity
write(io::Base64.Base64EncodePipe, vol::NIVolume{UInt8,1}) = invoke(write, (IO, NIVolume{UInt8,1}), io, vol)

# Write a NIfTI file
function write(io::IO, vol::NIVolume)
    write(io, niupdate(vol).header)
    if isempty(vol.extensions)
        write(io, Int32(0))
    else
        for ex in vol.extensions
            sz = esize(ex)
            write(io, Int32(sz))
            write(io, Int32(ex.ecode))
            write(io, ex.edata)
            write(io, zeros(UInt8, sz - length(ex.edata)))
        end
    end
    if eltype(vol.raw) == Bool
        write(io, BitArray(vol.raw))
    else
        write(io, vol.raw)
    end
end

"""
    niwrite(path::AbstractString, vol::NIVolume)

Write a NIVolume to a file specified by `path`.
"""
function niwrite(path::AbstractString, vol::NIVolume)
    if split(path,".")[end] == "gz"
        io = open(path, "w")
        stream = GzipCompressorStream(io)
        write(stream, vol)
        close(stream)
        close(io)
    else
        io = open(path, "w")
        write(io, vol)
        close(io)
    end
end

# Read header from a NIfTI file
function read_header(io::IO)
    header, swapped = read(io, NIfTI1Header)
    if header.sizeof_hdr != SIZEOF_HDR1
        error("This is not a NIfTI-1 file")
    end
    header, swapped
end

# Look for a gzip header in an IOStream
function isgz(io::IO)
    try
        ret = read(io, UInt8) == 0x1F && read(io, UInt8) == 0x8B
        seekstart(io)
        ret
    catch err
        if isa(err, EOFError)
            @debug "reading the file resulted in an EOF error and \nthe end of the file was read.\nNo more data was available to read from the filestream.\nIt is likely that the file was corrupted or is empty (0 bytes)."
            rethrow(err)
        end
    end
end

"""
    niread(file; mmap=false, mode="r")

Read a NIfTI file to a NIVolume. Set `mmap=true` to memory map the volume.
"""
function niread(file::AbstractString; mmap::Bool=false, mode::AbstractString="r")
    io = niopen(file, mode)
    hdr, swapped = read_header(io)
    ex = read_extensions(io, hdr.vox_offset - 352)

    if hdr.magic === NP1_MAGIC
        vol = read_volume(io, to_eltype(hdr.datatype), to_dimensions(hdr.dim), mmap)
    else
        vol = read_volume(niopen(hdr_to_img(file), mode), to_eltype(hdr.datatype), to_dimensions(hdr.dim), mmap)
    end

    if swapped && sizeof(eltype(vol)) > 1
        return NIVolume(hdr, ex, mappedarray(ntoh, hton, vol))
    else
        return NIVolume(hdr, ex, vol)
    end
end

# Allow file to be indexed like an array, but with indices yielding scaled data
@inline getindex(f::NIVolume{T}, idx::Vararg{Int}) where {T} =
    getindex(f.raw, idx...,) * f.header.scl_slope + f.header.scl_inter

add1(x::Union{AbstractArray{T},T}) where {T<:Integer} = x + 1
add1(::Colon) = Colon()

"""
    vox(f::NIVolume, args...,)

Get the value of a voxel from volume `f`, scaled by slope and intercept given in header, with 0-based indexing.
`args` are the voxel indices and the length should be the number of dimensions in `f`.
"""
@inline vox(f::NIVolume, args...,) = getindex(f, map(add1, args)...,)

size(f::NIVolume) = size(f.raw)
size(f::NIVolume, d) = size(f.raw, d)
ndims(f::NIVolume) = ndims(f.raw)
length(f::NIVolume) = length(f.raw)
lastindex(f::NIVolume) = lastindex(f.raw)

end


export
    AutoLevelSetEvaluator,
    evaluate_parameters!

const HARD_BRAKE_INDEX = 4

function build_generators_evaluator(flags)
    feature_dim = 0
    flags["num_scenarios"] = 1
    flags["num_monte_carlo_runs"] = 100
    flags["prime_time"] = 1.
    flags["sampling_period"] = .1
    flags["sampling_time"] = 5.
    flags["min_num_vehicles"] = 20
    flags["max_num_vehicles"] = 20
    flags["num_lanes"] = 3
    flags["roadway_length"] = 300.
    flags["roadway_radius"] = 50.
    flags["response_time"] = .4
    flags["min_base_speed"] = 15.
    flags["max_base_speed"] = 25.

    # roadway
    roadway = gen_stadium_roadway(flags["num_lanes"],
        length = flags["roadway_length"],
        radius = flags["roadway_radius"])

    # scene
    scene = Scene(flags["max_num_vehicles"])
    scene_gen = HeuristicSceneGenerator(
        flags["min_num_vehicles"],
        flags["max_num_vehicles"],
        flags["min_base_speed"],
        flags["max_base_speed"],
        flags["min_vehicle_length"],
        flags["max_vehicle_length"],
        flags["min_vehicle_width"],
        flags["max_vehicle_width"],
        flags["min_init_dist"])

    # behavior
    context = IntegratedContinuous(flags["sampling_period"], 1)
    params = [get_aggressive_behavior_params(
                            lon_σ = flags["lon_accel_std_dev"],
                            lat_σ = flags["lat_accel_std_dev"],
                            response_time = flags["response_time"]),
                        get_passive_behavior_params(
                            lon_σ = flags["lon_accel_std_dev"],
                            lat_σ = flags["lat_accel_std_dev"],
                            response_time = flags["response_time"]),
                        get_normal_behavior_params(
                            lon_σ = flags["lon_accel_std_dev"],
                            lat_σ = flags["lat_accel_std_dev"],
                            response_time = flags["response_time"])]
                weights = WeightVec([.3,.3,.4])
    behavior_gen = PredefinedBehaviorGenerator(context, params, weights)

    # evaluator
    max_num_scenes = Int(ceil(
        (flags["prime_time"] + flags["sampling_time"]) / flags["sampling_period"]))
    rec = SceneRecord(max_num_scenes, flags["sampling_period"],
        flags["max_num_vehicles"])
    features = Array{Float64}(feature_dim, 1, flags["max_num_vehicles"])
    targets = Array{Float64}(flags["target_dim"], flags["max_num_vehicles"])
    agg_targets = Array{Float64}(flags["target_dim"], flags["max_num_vehicles"])
    ext = EmptyExtractor()
    eval = MonteCarloEvaluator(
        ext,
        flags["num_monte_carlo_runs"],
        context,
        flags["prime_time"],
        flags["sampling_time"],
        flags["veh_idx_can_change"],
        rec,
        features,
        targets,
        agg_targets)

    return (roadway, scene_gen, behavior_gen, eval)
end

type AutoLevelSetEvaluator <: LevelSetEvaluator
    roadway::Roadway
    scene_gen::SceneGenerator
    behavior_gen::BehaviorGenerator
    eval::Evaluator
    scene::Scene
    models::Dict{Int, DriverModel}
    function AutoLevelSetEvaluator(
            roadway::Roadway,
            scene_gen::SceneGenerator,
            behavior_gen::BehaviorGenerator,
            eval::Evaluator,
            scene = Scene(),
            models = Dict{Int, DriverModel}())
        new(roadway, scene_gen, behavior_gen, eval, scene, models)
    end
end

"""
Description:
    - alternate constructor directly from flags
"""
function AutoLevelSetEvaluator()
    parse_flags!(FLAGS, ARGS)
    roadway, scene_gen, behavior_gen, eval = build_generators_evaluator(FLAGS)
    return AutoLevelSetEvaluator(roadway, scene_gen, behavior_gen, eval)
end

function update_parameters!(lseval::AutoLevelSetEvaluator, lon_σ::Float64,
        lat_σ::Float64)
    for params in lseval.behavior_gen.params
        params.idm.σ = lon_σ
        params.lat.σ = lat_σ
    end
end

"""
Description:
    - Run simulations to evaluate the probability of a hard brake given the
        parameter values

Args:
    - lse: automotive level set estimator
    - params: array of parameter values at which to evaluate the target value
        params[1] = standard deviation of longitudinal acceleration (lon_σ)
            - min: 0. m/s^2
            - max: 2. m/s^2
        params[1] = standard deviation of lateral acceleration (lat_σ)
            - min: 0. m/s^2
            - max: .5 m/s^2
    - scenario_seed: random seed from which to generate the scenario
    - simulation_seed: random seed to use for simulating the scenario

Returns:
    - value of the target (hard brake probability)
        - min: 0.
        - max: 1.
"""
function evaluate_parameters!(lse::AutoLevelSetEvaluator,
        params::Array{Float64}, scenario_seed::Int = 1, simulation_seed::Int = 1)
    update_parameters!(lse, params[1], params[2])
    reset!(lse.scene_gen, lse.scene, lse.roadway, scenario_seed)
    reset!(lse.behavior_gen, lse.models, lse.scene, scenario_seed)
    evaluate!(lse.eval, lse.scene, lse.models, lse.roadway, simulation_seed)
    return mean(lse.eval.agg_targets, 2)[HARD_BRAKE_INDEX]
end


isdefined(Base, :__precompile__) && __precompile__()

module MAVs
using NLOptControl
using VehicleModels

include("CaseModule.jl")
using .CaseModule

include("AutonomousControl.jl")
#include("SharedControl.jl")
#include("SimpleModel.jl")
#include("PathFollowing.jl")

using .AutonomousControl
#using .SharedControl
#using .SimpleModel
#using .PathFollowing

export

      # CaseModule.jl
      Case,
      defineCase,
      setWeights!,
      setMisc!,
      setupResults,
      case2dfs,  # currently not working
      dataSet,
      Obs,
      defineObs,

      # AutonomousControl.jl
      initializeAutonomousControl,
      autonomousControl!,
      updateAutoParams!

      # SharedControl.jl
    #  initializeSharedControl!,
    #  checkFeasibility!,
    #  sharedControl!,
    #  getPlantData!,
    #  sendOptData!,
    #  ExternalModel,

      # SimpleModel.jl
  #    initializeSimpleModel,
    #  updateSimpleModel,
  #    runSimpleModel,
  #    compareSimpleModels,
#      resetDesignVariables,

      # PathFollowing.jl
    #  initializePathFollowing,
    #  updatePathParams!
end


module TransformVariables

using ArgCheck: @argcheck
using DocStringExtensions: SIGNATURES, TYPEDEF
import ForwardDiff
using LinearAlgebra: UpperTriangular, logabsdet
using MacroTools: @capture
using Parameters: @unpack
using Random: AbstractRNG, GLOBAL_RNG

include("utilities.jl")
include("generic.jl")
include("scalar.jl")
include("special_arrays.jl")
include("aggregation.jl")
include("custom.jl")

end # module


abstract type AbstractOracleCost{Mdl} end

oracle_stripe(args...; kwargs...) = oracle_stripe(NoHint(), args...; kwargs...)
oracle_stripe(::AbstractHint, args...; kwargs...) = @assert false
function oracle_stripe(hint::AbstractHint, mdl, A, Π; kwargs...)
    return oracle_stripe(hint, mdl, A; kwargs...)
end

function bound_stripe(A, K, Π, ocl::AbstractOracleCost)
    return bound_stripe(A, K, Π, oracle_model(ocl))
end

function bound_stripe(A, K, ocl::AbstractOracleCost)
    return bound_stripe(A, K, oracle_model(ocl))
end

function bound_stripe(A, K, Π, mdl)
    return bound_stripe(A, K, mdl)
end

@inline cost_type(mdl) = cost_type(typeof(mdl))
@inline cost_type(::Type{<:AbstractOracleCost{Mdl}}) where {Mdl} = cost_type(Mdl)
@inline objective_identity(::typeof(+), T) = zero(T)
@inline objective_identity(::typeof(max), T) = typemin(T)

@inline bottleneck_value(A, Π, Φ, mdl) = compute_objective(max, A, Π, Φ, mdl)
@inline bottleneck_value(A, Φ, mdl) = compute_objective(max, A, Φ, mdl)
@inline total_value(A, Π, Φ, mdl) = compute_objective(+, A, Π, Φ, mdl)
@inline total_value(A, Φ, mdl) = compute_objective(+, A, Φ, mdl)

@inline function compute_objective(g::G, A, Π, Φ::SplitPartition, mdl) where {G}
    return compute_objective(g, A, Π, Φ, oracle_stripe(StepHint(), mdl, A, Π))
end
@inline function compute_objective(g::G, A, Π, Φ, mdl) where {G}
    Φ_dom = convert(DomainPartition, Φ)
    A_prm = A[:, Φ_dom.prm]
    Φ_spl = SplitPartition(length(Φ), Φ_dom.spl)
    return compute_objective(g, A_prm, Π, Φ_spl, mdl)
end
function compute_objective(g::G, A, Π, Φ::SplitPartition, mdl::AbstractOracleCost) where {G}
    cst = objective_identity(g, cost_type(mdl))
    for k = 1:Φ.K
        j = Φ.spl[k]
        j′ = Φ.spl[k + 1]
        cst = g(cst, mdl(j, j′, k))
    end
    return cst
end
@inline function compute_objective(g::G, A, Φ::SplitPartition, mdl) where {G}
    return compute_objective(g, A, Φ, oracle_stripe(StepHint(), mdl, A))
end
@inline function compute_objective(g::G, A, Φ, mdl) where {G}
    Φ_dom = convert(DomainPartition, Φ)
    A_prm = A[:, Φ_dom.prm]
    Φ_spl = SplitPartition(length(Φ), Φ_dom.spl)
    return compute_objective(g, A_prm, Φ_spl, mdl)
end
function compute_objective(g, A, Φ::SplitPartition, mdl::AbstractOracleCost) where {G}
    cst = objective_identity(g, cost_type(mdl))
    for k = 1:Φ.K
        j = Φ.spl[k]
        j′ = Φ.spl[k + 1]
        cst = g(cst, mdl(j, j′, k))
    end
    return cst
end

function compute_weight(A, weight)
    w = oracle_stripe(StepHint(), weight, A)
    m, n = size(A)
    wgt = undefs(cost_type(w), n)
    for j = 1:n
        wgt[j] = Step(w)(Same(j), Next(j + 1))
        Step(w)(Next(j), Same(j + 1))
    end
    return wgt
end

struct Extended{T}
    i::Bool
    x::T
end

infinity(::Type{Float16}) = Inf16
infinity(::Type{Float32}) = Inf32
infinity(::Type{Float64}) = Inf64
infinity(::Type{Extended{T}}) where {T} = Extended(true, zero(T))
infinity(x) = infinity(typeof(x))
infinity(T::Type) = Extended(true, zero(T))

extend(T::Type) = typeof(infinity(T))
#TODO use convert here
extend(x) = (x isa extend(typeof(x))) ? x : Extended(false, x)

#Base.zero(::Extended{T}) where {T} = extend(zero(T))
Base.zero(::Type{Extended{T}}) where {T} = extend(zero(T))
Base.:+(a::Extended, b::Extended) = Extended(a.i | b.i, a.x + b.x)
Base.:*(a::Extended, b::Extended) = Extended(a.i | b.i, a.x * b.x)
Base.typemax(::Type{Extended{T}}) where {T} = infinity(T)
Base.typemin(::Type{Extended{T}}) where {T} = extend(typemin(T))
Base.:<(a::Extended, b::Extended) = (!a.i && b.i) || ((!a.i && !b.i) && (a.x < b.x))
Base.:(==)(a::Extended, b::Extended) = (a.i && b.i) || ((!a.i && !b.i) && (a.x == b.x))
Base.isapprox(a::Extended, b::Extended; kwargs...) = (a.i && b.i) || ((!a.i && !b.i) && isapprox(a.x, b.x; kwargs...))

struct ConstrainedCost{F, W, Tw}
    f::F
    w::W
    w_max::Tw
    function ConstrainedCost{F, W, Tw}(f::F, w::W, w_max) where {F, W, Tw}
        @assert Tw == cost_type(W)
        return new{F, W, Tw}(f, w, w_max)
    end
end

ConstrainedCost(f::F, w::W, w_max) where {F, W} = ConstrainedCost{F, W, cost_type(W)}(f, w, w_max)

cost_type(::Type{ConstrainedCost{F, W, Tw}}) where {F, W, Tw} = extend(cost_type(F))

struct ConstrainedCostOracle{F, W, Tw} <: AbstractOracleCost{ConstrainedCost{F, W, Tw}}
    f::F
    w::W
    w_max::Tw
    function ConstrainedCostOracle{F, W, Tw}(f::F, w::W, w_max) where {F, W, Tw}
        @assert Tw == cost_type(W)
        return new{F, W, Tw}(f, w, w_max)
    end
end

ConstrainedCostOracle(f::F, w::W, w_max) where {F, W} = ConstrainedCostOracle{F, W, cost_type(W)}(f, w, w_max)

oracle_model(ocl::ConstrainedCostOracle) = ConstrainedCost(oracle_model(ocl.f), oracle_model(ocl.w), ocl.w_max)

function oracle_stripe(hint::AbstractHint, cst::ConstrainedCost{F, W, Tw}, A::SparseMatrixCSC, args...; kwargs...) where {F, W, Tw}
    (m, n) = size(A)
    f = oracle_stripe(hint, cst.f, A, args...; kwargs...)
    w = oracle_stripe(hint, cst.w, A, args...; kwargs...)
    w_max = cst.w_max
    return ConstrainedCostOracle(f, w, w_max)
end

@inline function (ocl::ConstrainedCostOracle)(j::Ti, j′::Ti, k...) where {Ti}
    if ocl.w(j, j′, k...) <= ocl.w_max
        return extend(ocl.f(j, j′, k...))
    else
        return infinity(cost_type(ocl.f))
    end
end

#TODO not sure about these two...
bound_stripe(A::SparseMatrixCSC, K, cst::ConstrainedCost) = bound_stripe(A, K, ocl.f)
bound_stripe(A::SparseMatrixCSC, K, ocl::ConstrainedCostOracle) = bound_stripe(A, K, ocl.f)

struct Infeasible end

struct Feasible end

Base.:<(::Feasible, ::Feasible) = false
Base.:<(::Feasible, ::Infeasible) = true
Base.:<(::Infeasible, ::Infeasible) = false
Base.:<(::Infeasible, ::Feasible) = false
Base.:(==)(::Feasible, ::Feasible) = true
Base.:(==)(::Feasible, ::Infeasible) = false
Base.:(==)(::Infeasible, ::Feasible) = false
Base.:(==)(::Infeasible, ::Infeasible) = true

struct FeasibleCost end

@inline (::FeasibleCost)(j, j′, k...) = Feasible()
@inline cost_type(::Type{FeasibleCost}) = Feasible
oracle_model(::FeasibleCost) = FeasibleCost()
oracle_stripe(::AbstractHint, ::FeasibleCost, ::SparseMatrixCSC; kwargs...) = FeasibleCost()
#bound_stripe(A::SparseMatrixCSC, K, ::FeasibleCost) = (Feasible(), Feasible()) #TODO ?

struct Next{T}
    arg::T
end
@inline destep(arg::Next) = arg.arg
struct Same{T}
    arg::T
end
@inline destep(arg::Same) = arg.arg
struct Prev{T}
    arg::T
end
@inline destep(arg::Prev) = arg.arg
struct Jump{T}
    arg::T
end
@inline destep(arg::Jump) = arg.arg

struct Step{Ocl}
    ocl::Ocl
end

@propagate_inbounds (ocl::Step{Ocl})(args...) where {Ocl} = ocl.ocl(maptuple(destep, args...)...)
#function (ocl::Step{Ocl})(args...) where {Ocl}
#    @info "whoopsies" ocl.ocl args
#    assert false
#end


"""
    convert(ListRecord, qrec::QueueRecord{E})

Converts a QueueRecord into the corresponding ListRecord.
"""
function Base.convert(::Type{ListRecord{S,D,I}}, qrec::QueueRecord{Entity{S,D,I}}) where {S,D,I}

    frames = Array{RecordFrame}(undef, nframes(qrec))
    states = Array{RecordState{S,I}}(undef, nstates(qrec))
    defs = Dict{I, D}()

    lo = 1
    for (i,pastframe) in enumerate(1-nframes(qrec) : 0)
        frame = qrec[pastframe]

        hi = lo-1
        for entity in frame
            hi += 1
            defs[entity.id] = entity.def
            states[hi] = RecordState{S,I}(entity.state, entity.id)
        end

        frames[i] = RecordFrame(lo, hi)
        lo = hi + 1
    end

    return ListRecord{S,D,I}(get_timestep(qrec), frames, states, defs)
end
Base.convert(::Type{ListRecord}, qrec::QueueRecord{Entity{S,D,I}}) where {S,D,I} = convert(ListRecord{S,D,I}, qrec)

"""
    convert(QueueRecord, lrec::ListRecord)

Converts a ListRecord into the corresponding QueueRecord{Entity{S,D,I}}.
Note that the timesteps for a ListRecord are not necessarily constant timesteps.
"""
function Base.convert(::Type{QueueRecord{Entity{S,D,I}}}, lrec::ListRecord{S,D,I}) where {S,D,I}

    N = nframes(lrec)
    M = maximum(n_objects_in_frame(lrec, i) for i in 1 : N)
    retval = QueueRecord(Entity{S,D,I}, N, get_timestep(lrec), M)

    frame = Frame(Entity{S,D,I}, M)
    for i in 1 : N
        get!(frame, lrec, i)
        update!(retval, frame)
    end

    return retval
end

"""
    get_sparse_lookup(lrec::ListRecord{S,D,I})

Converts a ListRecord into the corresponding sparse matrix containing the states, SparseMatrixCSC{S,Int}.
This requires I <: Integer.

In the sparse array, each column is the state of an entity, and reach row is a frame entry.

A tuple containing the sparse matrix and a dictionary mapping from ids to the column number are returned.
"""
function get_sparse_lookup(rec::ListRecord{S,D,I}) where {S,D,I<:Integer}

    # ids are by time of entry
    # translate id to index on range of 1:n
    id_lookup = Dict{I,Int}(id => index for (index, id) in
        (rec.defs |> keys |> collect |> sort |> enumerate))

    m = nframes(rec) # num rows
    n = nids(rec) # num cols
    n_states = length(rec.states)

    # the row/frame of each state
    Is = Vector{Int}(undef, n_states)
    # the column/car index (not the id, see id_lookup)
    Js = similar(Is)
    # the states themselves
    Vs = similar(Is, S)

    # the index into I, J, and V (on [1:n_states])
    idx = 1
    for (fid, frame) in enumerate(rec.frames)
        for stateid in frame.lo : frame.hi
            recstate = rec.states[stateid]
            Is[idx] = fid
            Js[idx] = id_lookup[recstate.id]
            Vs[idx] = recstate.state

            idx += 1
        end
    end

    sparsemat = sparse(Is, Js, Vs, m, n)
    return (sparsemat, id_lookup)
end


module LightOSM

using Parameters
using DataStructures: DefaultDict, OrderedDict, MutableLinkedList, PriorityQueue, dequeue!, dequeue_pair!
using Statistics: mean
using SparseArrays: SparseMatrixCSC, sparse
using Graphs: AbstractGraph, DiGraph, nv, outneighbors, weakly_connected_components, vertices, all_neighbors, indegree, outdegree, add_edge!
using StaticGraphs: StaticDiGraph
using SimpleWeightedGraphs: SimpleWeightedDiGraph
using MetaGraphs: MetaDiGraph
using NearestNeighbors: KDTree, knn
using HTTP
using JSON
using LightXML
using DataFrames
using GeoInterface
using RecipesBase

export GeoLocation,
       AbstractOSMGraph,
       OSMGraph,
       SimplifiedOSMGraph,
       Node,
       Way,
       Restriction,
       Building,
       distance,
       heading,
       calculate_location,
       download_osm_network,
       graph_from_object,
       graph_from_download,
       graph_from_file,
       shortest_path,
       weights_from_path,
       total_path_weight,
       nearest_node,
       download_osm_buildings,
       buildings_from_object,
       buildings_from_download,
       buildings_from_file,
       simplify_graph,
       node_gdf,
       edge_gdf,
       highway_gdf

include("types.jl")
include("constants.jl")
include("utilities.jl")
include("geometry.jl")
include("download.jl")
include("parse.jl")
include("graph.jl")
include("traversal.jl")
include("shortest_path.jl")
include("nearest_node.jl")
include("buildings.jl")
include("simplification.jl")
include("geodataframes.jl")
include("plotrecipes.jl")

end # module


include("../../utils.jl")
using Random

# This program generates random length strings made up of ACTG characters.
# The idea is that there will be a significant number of repeated strings.
# The repeated strings are counted but the strings themselves are garbage.
# The result should be significant multi-sized fragmentation in the heap.

mutable struct TreeNode
   key::String
   count::Int
   left::TreeNode
   right::TreeNode
   next::TreeNode
   TreeNode() = new()
   TreeNode(x) = new(x,1)
   TreeNode(x,y) = new(x,y)
   TreeNode(n::TreeNode) = new(n.key, n.count)
end

function getKey(x::TreeNode) return x.key end
function getCount(x::TreeNode) return x.count end

function compare(g, x::TreeNode, y::TreeNode)
   if (g(x) < g(y))
      return -1
   elseif (g(x) == g(y))
      return 0
   else
      return 1
   end
end

function compareCount(x::TreeNode, y::TreeNode)
  return compare(getCount, x, y)
end

function compareKey(x::TreeNode, y::TreeNode)
  return compare(getKey, x, y)
end

function duplicateKey(root::TreeNode, n::TreeNode)
  root.count = root.count + 1;
end

function duplicateCount(root::TreeNode,n::TreeNode)
  if !isdefined(root,:next)
     root.next = n
  else
     n.next = root.next
     root.next = n
  end
end

function insert(root::TreeNode,n::TreeNode,compare,duplicate)
   result = compare(root,n)
   if result < 0
      if !isdefined(root,:left)
         root.left = TreeNode(n)
      else
         insert(root.left, n, compare, duplicate)
      end
   elseif result > 0
      if !isdefined(root,:right)
         root.right = TreeNode(n)
      else
         insert(root.right, n, compare, duplicate)
      end
   else
      duplicate(root,n)
  end
end

function traverse(n::TreeNode, f)
   if isdefined(n,:left)
      traverse(n.left, f)
   end

   f(n)

   if isdefined(n,:right)
      traverse(n.right, f)
   end
end

function print(n::TreeNode)
   count = 1
   while (isdefined(n,:next))
      n = n.next
      count = count + 1
   end
   println("There was/were ", count,  " string(s) that was/were repeated ", n.count, " times")
end

function tree(root::TreeNode, n)
   for i in 1:n
      insert(root, TreeNode(randstring("ACTG", rand(1:32))), compareKey,duplicateKey)
   end
end

resultRoot = TreeNode("end")

function insertHelper(n::TreeNode)
   insert(resultRoot, n, compareCount, duplicateCount)
end

#Build the tree sorted by count
function SortTree(n::TreeNode)
   traverse(n::TreeNode, insertHelper)
   return resultRoot
end

function test(n)
   startroot = TreeNode("start")
   buildtree = tree(startroot, n)
   result = SortTree(startroot)
#   println("done with sort")
#   traverse(result, print)
   return 6847
end

@gctime test(1024 * 1024 * 8)