export d2u, j2md, md2j, parsetimewin, timestamp, u2d, timespec

function tstr(t::DateTime)
  Y, M, D, h, m, s, μ = year(t), month(t), day(t), hour(t), minute(t), second(t), millisecond(t)
  Y = lpad(Y, 4, "0")
  M = lpad(M, 2, "0")
  D = lpad(D, 2, "0")
  h = lpad(h, 2, "0")
  m = lpad(m, 2, "0")
  s = lpad(s, 2, "0")
  μ = lpad(μ, 3, "0")
  return string(Y, "-", M, "-", D, "T", h, ":", m, ":", s, ".", μ)
end

"Alias to Dates.unix2datetime"
u2d(k::Real) = Dates.unix2datetime(k)
"Alias to Dates.datetime2unix"
d2u(k::DateTime) = Dates.datetime2unix(k)

@doc """
    timestamp()

Return current time formatted YYYY-mm-ddTHH:MM:SS.nnn.
""" timestamp
timestamp() = tstr(Dates.unix2datetime(time()))
timestamp(t::DateTime) = tstr(t)
timestamp(t::Real) = tstr(u2d(t))
timestamp(t::String) = tstr(Dates.DateTime(t))
tnote(s::String) = string(timestamp(), ": ", s)

"""
  m,d = j2md(y,j)

Convert Julian day j of year y to month m, day d
"""
function j2md(y::T, j::T) where T<:Integer
  if T != Int32
    y = Int32(y)
    j = Int32(j)
  end
  z = zero(Int32)
  o = one(Int32)
  m = z
  d = o
  if j > Int32(31)
    leapyear = ((j > 59) && ((y % Int32(400) == z) || (y % Int32(4) == z && y % Int32(100) != z)))
    while j > z
      d = j
      m += o
      j -= days_per_month[m]
      if leapyear && m == 2
        j -= o
      end
    end
  else
    m = o
    d = j
  end
  return m,d
end

"""
  j = md2j(y,m,d)

Convert month `m`, day `d` of year `y` to Julian day (day of year)
"""
function md2j(y::T, m::T, d::T) where T<:Integer
  j = zero(Int32)
  if T != Int32
    y = Int32(y)
    m = Int32(m)
    d = Int32(d)
  end
  z = zero(Int32)
  i = one(Int8)
  while i < m
    j = j+getindex(days_per_month, i)
    i = i+1
  end
  j = j+d
  if m > 2 && ((y % Int32(400) == z) ||
               (y % Int32(4)   == z &&
                y % Int32(100) != z))
    j = j+1
  end
  return T(j)
end
md2j(y::AbstractString, m::AbstractString, d::AbstractString) = md2j(parse(Int, y), parse(Int, m), parse(Int, d))

@doc """
# Time Specification

Most functions that allow time specifiation use two reserved key words to track
time:
* `s`: Start (begin) time
* `t`: Termination (end) time

The values passed to keywords `s` and `t` can be real numbers, DateTime objects,
or ASCII strings. Strings must follow the format "yyyy-mm-ddTHH:MM:SS.nnn", e.g.
`s="2016-03-23T11:17:00.333"`. Exact behavior depends on the types of s and t:

| **s** | **t** | **Behavior**                         |
|:------|:------|:-------------------------------------|
| DT    | DT    | Sort                                 |
| R     | DT    | Add `s` seconds to `t`, then sort    |
| DT    | R     | Add `t` seconds to `s`, then sort    |
| S     | R, DT | Convert `s` → DateTime, then sort    |
| R, DT | S     | Convert `t` → DateTime, then sort    |
| R     | R     | `s` and `t` are seconds from `now()` |

(above, R = Real, DT = DateTime, S = String, I = Integer)

## Relative Timekeeping
Numeric time values are interpreted *relative to the start of the current minute*.
Thus, if `-s` or `-t` is 0, the data request begins (or ends) at the start of
the minute in which the request is submitted.
""" timespec
timespec() = nothing

"""
d0, d1 = parsetimewin(s, t)

Convert times `s` and `t` to strings and sorts s.t. d0 < d1.

See also: ?timespec
"""
function parsetimewin(s::DateTime, t::DateTime)
  if s < t
    return (string(s), string(t))
  else
    return (string(t), string(s))
  end
end
parsetimewin(s::DateTime, t::String) = parsetimewin(s, DateTime(t))
parsetimewin(s::DateTime, t::Real) = parsetimewin(s, u2d(d2u(s)+t))
parsetimewin(s::Real, t::DateTime) = parsetimewin(t, u2d(d2u(t)+s))
parsetimewin(s::String, t::Union{Real,DateTime}) = parsetimewin(DateTime(s), t)
parsetimewin(s::Union{Real,DateTime}, t::String) = parsetimewin(s, DateTime(t))
parsetimewin(s::String, t::String) = parsetimewin(DateTime(s), DateTime(t))
parsetimewin(s::Real, t::Real) = parsetimewin(u2d(60*floor(Int, time()/60) + s), u2d(60*floor(Int, time()/60) + t))

# =========================================================
# Not for export
function y2μs(y::T) where T<:Integer
  y = Int64(y)-1
  return 86400000000 * (y*365 + div(y,4) - div(y,100) + div(y,400)) - 62135596800000000
end

# ts = round(Int64, d2u(DateTime(iv[1], m, d, iv[3], iv[4], iv[5], iv[6]))*sμ
mktime(y::T, j::T, h::T, m::T, s::T, μ::T) where T<:Integer = (y2μs(y) +
          Int64(j-one(T))*86400000000 +
          Int64(h)*3600000000 +
          Int64(m)*60000000 +
          Int64(s)*1000000 +
          Int64(μ))
mktime(t::Array{T,1}) where T<:Integer =(y2μs(t[1]) +
          Int64(t[2]-one(T))*86400000000 +
          Int64(t[3])*3600000000 +
          Int64(t[4])*60000000 +
          Int64(t[5])*1000000 +
          Int64(t[6]))

# convert a formatted time string to integer μs from the Unix epoch
function tstr2int(s::String)
  str = split(s, ".", limit=2)
  if length(str) < 2
    μ = 0
  else
    μ = parse(Int64, rpad(str[2], 6, '0'))
  end
  return DateTime(str[1]).instant.periods.value*1000 - dtconst + μ
end

# convert a time in integer μs (measured from the Unix epoch) to a string
function int2tstr(t::Int64)
  dt = unix2datetime(div(t, 1000000))
  v = 1000*getfield(getfield(getfield(dt, :instant), :periods), :value)
  r = string(t - v + dtconst)
  s = string(dt) * "." * lpad(r, 6, '0')
  return s
end

# =========================================================
# Time windowing functions
"""
    endtime(t::Array{Int64,2}, Δ::Int64)

Compute the time of the last sample in SeisIO time matrix `t` sampled at
(integer μs sampling interval) Δ. Result is measured from the Unix epoch in
integer μs.
"""
function endtime(t::Array{Int64,2}, Δ::Int64)
  if isempty(t)
    t_end = 0
  else
    L = size(t,1)
    t_end = (t[L,1]-1)*Δ
    if L > 2
      t_end += getindex(sum(t, dims=1),2)
    else
      t_end += t[1,2]
    end
    # t_end = getindex(sum(t, dims=1),2) + (t[L,1]-1)*Δ
  end
  return t_end
end
function endtime(t::Array{Int64,2}, fs::Float64)
  if fs == 0.0
    return t[size(t,1), 2]
  else
    return endtime(t, round(Int64, 1.0/(fs*μs)))
  end
end

function t_expand(t::Array{Int64,2}, fs::Float64)
  fs == 0.0 && return t[:,2]
  t[end,1] == 1 && return [t[1,2]]
  dt = round(Int64, 1.0/(fs*μs))
  tt = dt.*ones(Int64, t[end,1])
  tt[1] -= dt
  for i = 1:size(t,1)
    tt[t[i,1]] += t[i,2]
  end
  cumsum!(tt, tt)
  return tt
end

function t_collapse(tt::Array{Int64,1}, fs::Float64)
  if fs == 0.0
    t = hcat(collect(1:1:length(tt)), tt)
  else
    dt = round(Int64, 1.0/(fs*μs))
    ts = Array{Int64,1}([dt; diff(tt)::Array{Int64,1}])
    L = length(tt)
    i = findall(ts .!= dt)
    t = Array{Int64,2}([[1 tt[1]];[i ts[i].-dt]])
    if isempty(i) || i[end] != L
      t = vcat(t, hcat(L,0))
    end
  end
  return t
end

function x_inds(t::Array{Int64,2})
  nt = size(t, 1)-1
  inds = zeros(Int64, nt, 2)
  for i in 1:nt
    inds[i,1] = t[i,1]
    inds[i,2] = t[i+1,1] - (i == nt ? 0 : 1)
  end
  return inds
end

function t_win(T::Array{Int64,2}, Δ::Int64)
  isempty(T) && return(T)
  n = size(T,1)-1
  if T[n+1,2] != 0
    T = vcat(T, [T[n+1,1] 0])
    n += 1
  end
  w0 = -(Δ)
  W = Array{Int64,2}(undef,n,2)
  for i = 1:n
    W[i,1] = T[i,2] + w0 + Δ
    W[i,2] = W[i,1] + Δ*(T[i+1,1]-T[i,1]-1)
    w0 = W[i,2]
  end
  W[n,2] += Δ
  return W
end
t_win(T::Array{Int64,2}, fs::Float64) = t_win(T, round(Int64, 1000000.0/fs))

function w_time(W::Array{Int64,2}, Δ::Int64)
  n = size(W,1)+1
  T = Array{Int64,2}(undef,n,2)
  T[1,1] = Int64(1)
  T[1,2] = W[1,1]
  for i = 2:n-1
    T[i,1] = T[i-1,1] + div(W[i-1,2]-W[i-1,1], Δ) + 1
    T[i,2] = W[i,1] - W[i-1,2] - Δ
  end
  T[n,1] = T[n-1,1] + div(W[n-1,2]-W[n-1,1], Δ)
  T[n,2] = 0
  if T[n,1] == T[n-1,1]
    T = T[1:n-1,:]
  end
  return T
end
w_time(W::Array{Int64,2}, fs::Float64) = w_time(W, round(Int64, 1000000.0/fs))

function unpack_u8(v::UInt8)
  a = signed(div(v,0x10))*Int8(10)
  b = signed(rem(v,0x10))
  return Int64(a+b)
end

function datehex2μs!(a::Array{Int64,1}, datehex::Array{UInt8,1})
  a[1] = 100*unpack_u8(getindex(datehex, 1)) + unpack_u8(getindex(datehex, 2))
  a[2] = md2j(getindex(a,1), unpack_u8(getindex(datehex, 3)), unpack_u8(getindex(datehex, 4))) - 1
  setindex!(a, y2μs(getindex(a,1)), 1)
  a[3] = unpack_u8(getindex(datehex, 5))
  a[4] = unpack_u8(getindex(datehex, 6))
  a[5] = unpack_u8(getindex(datehex, 7))
  a[6] = unpack_u8(getindex(datehex, 8))
  return a[1] + a[2]*86400000000 + a[3]*3600000000 + a[4]*60000000 + a[5]*1000000 + a[6]*10000
end

function tx_float(t::Array{Int64,2}, fs::Float64)
  fs == 0.0 && return map(Float64, t[:,2])
  t[end,1] == 1 && return Float64[t[1,2]]
  Nt = t[end,1]
  dt = 1.0/fs
  tt = dt.*ones(Float64, Nt)
  tt[1] -= dt
  for i = 2:size(t,1)
    tt[t[i,1]] += t[i,2]
  end
  cumsum!(tt, tt)
  return tt
end


import MPI

# ------------------------------------
# mpi
# ------------------------------------
function set_mpi(cellxmax, cellymax)
    comm = MPI.COMM_WORLD        # MPIプロセスの呼び出し
    rank = MPI.Comm_rank(comm)   # MPIの分割, 各プロセス番号(0~n)
    size = MPI.Comm_size(comm)   # MPIの分割, 各プロセス番号(0~n)

    num_div = 0.0      # 各方向の分割数
    if floor(size^0.5 * 10000) % 10000 != 0 || size <= 0.0
        println("------------------------\n")
        println(" mpi error \n")
        println(" please change parallel number to be an number of squares \n")
        println("------------------------")
        throw(UndefVarError(:x))
    else
        num_div = Int(size^0.5)
    end

    Nx = cellxmax-4 # 仮想セル分を引く
    Ny = cellymax-4 # 仮想セル分を引く

    #=
    並列時のstart i, end i, 分割数cellNxを定義
    =#
    cellNx, ista, iend, cellNy, jsta, jend = start_and_end0(Nx, Ny, num_div, rank)

    return cellNx, ista, iend, cellNy, jsta, jend, num_div, rank, size, comm
end

# ---------------------------------------------
# mpi division
# ---------------------------------------------
function start_and_end0(Nx, Ny, num_div, rank)

    if Nx % num_div != 0
        println("------------------------\n")
        println(" mpi error \n ")
        println(" please change Nx \n ")
        println(" Nx = ")
        println(Nx)
        println(" \n ")
        println(" num_div = ")
        println(num_div)
        println("------------------------")
    elseif Ny % num_div != 0
        println("------------------------\n")
        println(" mpi error \n ")
        println(" please change Ny \n ")
        println(" Ny = ")
        println(Ny)
        println(" \n ")
        println(" num_div = ")
        println(num_div)
        println("------------------------")
    end

    cellNx = div(Nx, num_div)   # プロセスの分割数
    cellNy = div(Ny, num_div)   # プロセスの分割数
    ista = rank * cellNx + 1  # プロセス毎のstart i (0-> 0*n+1 = 1,        1-> 1*n+1 = n+1)
    iend = ista + cellNx - 1  # プロセス毎のend i   (0-> 0*n+1 + n-1 = n , 1-> 1*n+1 + n-1 = 2n)
    jsta = rank * cellNy + 1  # プロセス毎のstart i (0-> 0*n+1 = 1,        1-> 1*n+1 = n+1)
    jend = ista + cellNy - 1  # プロセス毎のend i   (0-> 0*n+1 + n-1 = n , 1-> 1*n+1 + n-1 = 2n)

    return cellNx, ista, iend, cellNy, jsta, jend
end

function set_value_for_mpi(Qbase, cellx_mpi, celly_mpi, volume, cellcenter, wally, dx, dy,
                        Qbase_all, volume_all, cellcenter_all, wally_all, dx_all, dy_all, nval, num_div, rank)

    is = 3
    ie = cellx_mpi-2
    js = 3
    je = celly_mpi-2
    for l in 1:nval
        for i in is:ie
            for j in js:je
                ii, jj = Conversion_to_coordinate_numbers(i, j, cellx_mpi, celly_mpi, num_div, rank)
                Qbase[i,j,l]  = Qbase_all[ii, jj, l]
            end
        end
    end
    for i in is:ie
        for j in js:je
            ii, jj = Conversion_to_coordinate_numbers(i, j, cellx_mpi, celly_mpi, num_div, rank)
            volume[i,j] = volume_all[ii, jj]
            wally[i,j]  = wally_all[ii, jj]
            dx[i,j]     = dx_all[ii, jj]
            dy[i,j]     = dy_all[ii, jj]
        end
    end

    return Qbase, volume, cellcenter, wally, dx, dy
end

function Conversion_to_coordinate_numbers(i, j, cellx_mpi, celly_mpi, num_div, rank)
    #=
    div system

      rank      div(rank, size^0.5)    rem(rank, size^0.5)

      2 | 3            1 | 1                  0 | 1
     -------          -------                -------
      0 | 1            0 | 0                  0 | 1

      y
      ↑
       →x

    =#
    rankx = rem(rank, num_div)
    ranky = div(rank, num_div)

    ii = i + rankx*(cellx_mpi-2)
    jj = j + ranky*(celly_mpi-2)

    return ii, jj
end

function set_vecA_mpi(vecAx_all, vecAy_all, vecAx, vecAy, cellx_mpi, celly_mpi, num_div, rank)
    #=
    vecA system

                    vecAy[2,3]
                |---------------|
                |               |
                |               |
    vecAx[2,2]  |   cell(2,2)   | vecAx[3,2]
                |               |
                |               |
                |---------------|
                    vecAy[2,2]

    =#
    is = 1
    ie = cellx_mpi+1
    js = 1
    je = celly_mpi
    for l in 1:2
        for i in is:ie
            for j in js:je
                ii, jj = Conversion_to_coordinate_numbers_atbound(i, j, cellx_mpi, celly_mpi, num_div, rank)
                vecAx[i,j,l]  = vecAx_all[ii, jj, l]
            end
        end
    end

    is = 1
    ie = cellx_mpi
    js = 1
    je = celly_mpi+1
    for l in 1:2
        for i in is:ie
            for j in js:je
                ii, jj = Conversion_to_coordinate_numbers_atbound(i, j, cellx_mpi, celly_mpi, num_div, rank)
                vecAy[i,j,l]  = vecAy_all[ii, jj, l]
            end
        end
    end

    return vecAx, vecAy
end

function Conversion_to_coordinate_numbers_atbound(i, j, cellx_mpi, celly_mpi, num_div, rank)
    #=
    div system

      rank      div(rank, size^0.5)    rem(rank, size^0.5)

      2 | 3            1 | 1                  0 | 1
     -------          -------                -------
      0 | 1            0 | 0                  0 | 1

    =#
    rankx = rem(rank, num_div)
    ranky = div(rank, num_div)

    ii = i + rankx*(cellx_mpi)
    jj = j + ranky*(celly_mpi)

    return ii, jj
end

function mpi_div_3d(ite, ls, le, js, je, is, ie)
    #= ite: iteration number (ista, iend generated by start_and_end(N,size))
        ls: start number at l
        le: end number at l
        js:
        je:
        is:
        ie:
        div():商
        rem():余り
    =#
    Ni = ie - is + 1 # number of i ite
    Nj = je - js + 1 # number of j ite
    #Nl = le - ls + 1 # number of l ite

    l = ls + div(ite-1, Ni*Nj)

    ite = ite - div(ite-1, Ni*Nj) * Ni*Nj

    j = js + div(ite-1, Ni)

    ite = ite - div(ite-1, Ni) * Ni

    i = is + div(ite-1, 1)

    return i, j, l
end

function mpi_div_2d(ite, js, je, is, ie)
    #= ite: iteration number (ista, iend generated by start_and_end(N,size))
        js:
        je:
        is:
        ie:
        div():商
        rem():余り
    =#
    Ni = ie - is + 1 # number of i ite

    j = js + div(ite-1, Ni)

    ite = ite - div(ite-1, Ni) * Ni

    i = is + div(ite-1, 1)

    return i, j
end


# Test if tf listner works correctly
using RobotOS
@rosimport std_msgs.msg: Bool
rostypegen(@__MODULE__)
import .std_msgs.msg: BoolMsg

using ROSTransforms.TF
using Test

# Initialize node
init_node("listner", anonymous=true) # delete

# Setup the transform listener
tl = TransformListener()
# Wait and collect the transform
waitForTransform(tl, "parent_link", "child_link", Time(), Duration(1.0))
transform = lookupTransform(tl, "parent_link", "child_link", Time())

@testset "TransformListener" begin
    tf_correct = Transform([1, 2, 3.], [0, 0, 0, 1.])
    @test transform == tf_correct
end

# Publsih success
pub = Publisher{BoolMsg}("/success", queue_size=10)
for i ∈ 1:100
    publish(pub, BoolMsg(true))
    rossleep(.1)
end


using Dates, Learn, BusinessDays

struct HolidaysParsed
    weekday::String
    date::Date
    name::String
    localname::String
    holidaytype::String
    comments::String
end
# HolidaysParsed(weekday, date, name, localname, holidaytype, comments) = begin
#     date = Date(date.children[1].attributes["datetime"])
#     HolidaysParsed(nodeText(weekday), date, nodeText(name), nodeText(localname), nodeText(holidaytype), nodeText(comments))
# end

# How I got the testcases for sweden

# using HTTP, Gumbo, Cascadia
# HolidaysParsed(weekday, date, name, localname, holidaytype, comments) = begin
#     date = Date(date.children[1].attributes["datetime"])
#     HolidaysParsed(nodeText(weekday), date, nodeText(name), nodeText(localname), nodeText(holidaytype), nodeText(comments))
# end

# function fetch_holidays(yr::Year)
#     url = "https://www.officeholidays.com/countries/sweden/$(yr.value)"
#     r = HTTP.get(url; require_ssl_verification=false)
#     page = parsehtml(String(r.body))
#     rows = eachmatch(sel".country-table tr", page.root)[2:end]

#     holidays = [HolidaysParsed(eachmatch(sel"td", row)...) for row in rows]
#     [h for h in holidays if h.holidaytype == "National Holiday"]
# end

# hs = [((fetch_holidays(Year(yr)) for yr in 2015:2022)...)...]

# open(joinpath(@__DIR__,"./testHolidaysSweden.txt"), "w") do io
#     for h in hs
#         println(io, "$(h.weekday),$(h.date),$(h.name),$(h.localname),$(h.holidaytype),$(h.comments)")
#     end
# end

HolidaysParsed(weekday, date, name, localname, holidaytype, comments) = begin
    date = Date(date)
    HolidaysParsed(String(weekday), date, String(name), String(localname), String(holidaytype), String(comments))
end

hs = open(joinpath(@__DIR__,"./testHolidaysSweden.txt")) do io
    readlines(io) .|> x -> split(x,",") |> x->HolidaysParsed(x...)
end

holidays = Dict(h.date=>h for h in hs)

@testset "SwedishHolidays" begin
    for d ∈ Date(2015,1,1):Day(1):Date(2022,12,31)
        @test BusinessDays.isweekend(d) || ((d ∉ holidays.keys) == isbday(SwedenCalendar(),d))
    end
end


verbose = true
 if verbose && !isdefined(Main,:RvSpectML)   println("# Loading RvSpecML")    end
 using RvSpectML

# USER: You must create a data_paths.jl file in one of the default_paths_to_search listed below. It need only contain one line:
# expres_data_path = "/path/to/EXPRES/data/not/including/target_subdir"
target_subdir = "101501"   # USER: Replace with directory of your choice
 fits_target_str = "101501"
 output_dir = "examples/output/"
 default_paths_to_search = [pwd(),"examples",joinpath(pkgdir(RvSpectML),"examples"),"/gpfs/group/ebf11/default/ebf11/expres/inputs"]
 # NOTE: make_manifest does not update its paths_to_search when default_paths_to_search is defined here, so if you change the line above, you must also include "paths_to_search=default_paths_to_search" in the make_manifest() function call below
 pipeline_plan = PipelinePlan()
 dont_make_plot!(pipeline_plan, :movie)

reset_all_needs!(pipeline_plan)
 if need_to(pipeline_plan,:read_spectra)
   if verbose println("# Finding what data files are avaliable.")  end
   df_files = make_manifest(target_subdir, EXPRES )

   if verbose println("# Reading in customized parameters from param.jl.")  end
   eval(code_to_include_param_jl())

   if verbose println("# Reading in ", size(df_files_use,1), " FITS files.")  end
   @time all_spectra = map(EXPRES.read_data,eachrow(df_files_use))
   RvSpectML.discard_blaze(all_spectra)
   RvSpectML.discard_continuum(all_spectra)
   dont_need_to!(pipeline_plan,:read_spectra)
   #all_spectra
 end


"""
    AbstractDiffBackend

An abstract type for diff backends. See [`FiniteDifferencesBackend`](@ref) for
an example.
"""
abstract type AbstractDiffBackend end

struct NoneDiffBackend <: AbstractDiffBackend end

"""
    _derivative(f, t[, backend::AbstractDiffBackend])

Compute the derivative of a callable `f` at time `t` computed using the given `backend`,
an object of type [`Manifolds.AbstractDiffBackend`](@ref). If the backend is not explicitly
specified, it is obtained using the function [`default_differential_backend`](@ref).

This function calculates plain Euclidean derivatives, for Riemannian differentiation see
for example [`differential`](@ref Manifolds.differential(::AbstractManifold, ::Any, ::Real, ::AbstractRiemannianDiffBackend)).

!!! note

    Not specifying the backend explicitly will usually result in a type instability
    and decreased performance.
"""
function _derivative end

_derivative(f, t) = _derivative(f, t, default_differential_backend())

function _derivative!(f, X, t, backend::AbstractDiffBackend=default_differential_backend())
    return copyto!(X, _derivative(f, t, backend))
end

"""
    _gradient(f, p[, backend::AbstractDiffBackend])

Compute the gradient of a callable `f` at point `p` computed using the given `backend`,
an object of type [`AbstractDiffBackend`](@ref). If the backend is not explicitly
specified, it is obtained using the function [`default_differential_backend`](@ref).

This function calculates plain Euclidean gradients, for Riemannian gradient calculation see
for example [`gradient`](@ref Manifolds.gradient(::AbstractManifold, ::Any, ::Any, ::AbstractRiemannianDiffBackend)).

!!! note

    Not specifying the backend explicitly will usually result in a type instability
    and decreased performance.
"""
function _gradient end

_gradient(f, p) = _gradient(f, p, default_differential_backend())

function _gradient!(f, X, p, backend::AbstractDiffBackend=default_differential_backend())
    return copyto!(X, _gradient(f, p, backend))
end

"""
    _jacobian(f, p[, backend::AbstractDiffBackend])

Compute the jacobian of a callable `f` at point `p` computed using the given `backend`,
an object of type [`AbstractDiffBackend`](@ref). If the backend is not explicitly
specified, it is obtained using the function [`default_differential_backend`](@ref).

This function calculates plain Euclidean gradients, for Riemannian gradient calculation see
for example [`gradient`](@ref Manifolds.gradient(::AbstractManifold, ::Any, ::Any, ::AbstractRiemannianDiffBackend)).

!!! note

    Not specifying the backend explicitly will usually result in a type instability
    and decreased performance.
"""
function _jacobian end

_jacobian(f, p) = _jacobian(f, p, default_differential_backend())

function _jacobian!(f, X, p, backend::AbstractDiffBackend=default_differential_backend())
    return copyto!(X, _jacobian(f, p, backend))
end

"""
    CurrentDiffBackend(backend::AbstractDiffBackend)

A mutable struct for storing the current differentiation backend in a global
constant [`_current_default_differential_backend`](@ref).

# See also

[`AbstractDiffBackend`](@ref), [`default_differential_backend`](@ref), [`set_default_differential_backend!`](@ref)
"""
mutable struct CurrentDiffBackend
    backend::AbstractDiffBackend
end

"""
    _current_default_differential_backend

The instance of [`Manifolds.CurrentDiffBackend`](@ref) that stores the globally default
differentiation backend.
"""
const _current_default_differential_backend = CurrentDiffBackend(NoneDiffBackend())
"""
    default_differential_backend() -> AbstractDiffBackend

Get the default differentiation backend.
"""
default_differential_backend() = _current_default_differential_backend.backend

"""
    set_default_differential_backend!(backend::AbstractDiffBackend)

Set current backend for differentiation to `backend`.
"""
function set_default_differential_backend!(backend::AbstractDiffBackend)
    _current_default_differential_backend.backend = backend
    return backend
end

@doc raw"""
    ODEExponentialRetraction{T<:AbstractRetractionMethod, B<:AbstractBasis} <: AbstractRetractionMethod

Approximate the exponential map on the manifold by evaluating the ODE descripting the geodesic at 1,
assuming the default connection of the given manifold by solving the ordinary differential
equation

```math
\frac{d^2}{dt^2} p^k + Γ^k_{ij} \frac{d}{dt} p_i \frac{d}{dt} p_j = 0,
```

where ``Γ^k_{ij}`` are the Christoffel symbols of the second kind, and
the Einstein summation convention is assumed.

See [`solve_exp_ode`](@ref) for further details.

# Constructor

    ODEExponentialRetraction(
        r::AbstractRetractionMethod,
        b::AbstractBasis=DefaultOrthogonalBasis(),
    )

Generate the retraction with a retraction to use internally (for some approaches)
and a basis for the tangent space(s).
"""
struct ODEExponentialRetraction{T<:AbstractRetractionMethod,B<:AbstractBasis} <:
       AbstractRetractionMethod
    retraction::T
    basis::B
end
function ODEExponentialRetraction(r::T) where {T<:AbstractRetractionMethod}
    return ODEExponentialRetraction(r, DefaultOrthonormalBasis())
end
function ODEExponentialRetraction(::T, b::CachedBasis) where {T<:AbstractRetractionMethod}
    return throw(
        DomainError(
            b,
            "Cached Bases are currently not supported, since the basis has to be implemented in a surrounding of the start point as well.",
        ),
    )
end
function ODEExponentialRetraction(r::ExponentialRetraction, ::AbstractBasis)
    return throw(
        DomainError(
            r,
            "You can not use the exponential map as an inner method to solve the ode for the exponential map.",
        ),
    )
end
function ODEExponentialRetraction(r::ExponentialRetraction, ::CachedBasis)
    return throw(
        DomainError(
            r,
            "Neither the exponential map nor a Cached Basis can be used with this retraction type.",
        ),
    )
end


import Base: convert, haskey, setindex!, getindex
export slider, togglebutton, button,
       checkbox, textbox, textarea,
       radiobuttons, dropdown, select,
       togglebuttons, html, latex,
       progress, widget

const Empty = VERSION < v"0.4.0-dev" ? Nothing : Void

### Input widgets

########################## Slider ############################

type Slider{T<:Number} <: InputWidget{T}
    signal::Signal{T}
    label::AbstractString
    value::T
    range::Range{T}
    readout_format::AbstractString
    continuous_update::Bool
end

# differs from median(r) in that it always returns an element of the range
medianelement(r::Range) = r[(1+length(r))>>1]

slider(args...) = Slider(args...)
"""
    slider(range; value, signal, label="", continuous_update=true)

Create a slider widget with the specified `range`. Optionally specify
the starting `value` (defaults to the median of `range`), provide the
(Reactive.jl) `signal` coupled to this slider, and/or specify a string
`label` for the widget.
"""
slider{T}(range::Range{T};
          value=medianelement(range),
          signal::Signal{T}=Signal(value),
          label="",
          readout_format=T <: Integer ? "d" : ".3f",
          continuous_update=true) =
              Slider(signal, label, value, range, readout_format, continuous_update)

######################### Checkbox ###########################

type Checkbox <: InputWidget{Bool}
    signal::Signal{Bool}
    label::AbstractString
    value::Bool
end

checkbox(args...) = Checkbox(args...)

"""
    checkbox(value=false; label="", signal)

Provide a checkbox with the specified starting (boolean)
`value`. Optional provide a `label` for this widget and/or the
(Reactive.jl) `signal` coupled to this widget.
"""
checkbox(value::Bool; signal=Signal(value), label="") =
    Checkbox(signal, label, value)
checkbox(; label="", value=false, signal=Signal(value)) =
    Checkbox(signal, label, value)

###################### ToggleButton ########################

type ToggleButton <: InputWidget{Bool}
    signal::Signal{Bool}
    label::AbstractString
    value::Bool
end

togglebutton(args...) = ToggleButton(args...)

togglebutton(; label="", value=false, signal=Signal(value)) =
    ToggleButton(signal, label, value)

"""
    togglebutton(label=""; value=false, signal)

Create a toggle button. Optionally specify the `label`, the initial
state (`value=false` is off, `value=true` is on), and/or provide the
(Reactive.jl) `signal` coupled to this button.
"""
togglebutton(label; kwargs...) =
    togglebutton(label=label; kwargs...)

######################### Button ###########################

type Button{T} <: InputWidget{T}
    signal::Signal{T}
    label::AbstractString
    value::T
end

button(; value=nothing, label="", signal=Signal(value)) =
    Button(signal, label, value)

"""
    button(label; value=nothing, signal)

Create a push button. Optionally specify the `label`, the `value`
emitted when then button is clicked, and/or the (Reactive.jl) `signal`
coupled to this button.
"""
button(label; kwargs...) =
    button(label=label; kwargs...)

######################## Textbox ###########################

type Textbox{T} <: InputWidget{T}
    signal::Signal{T}
    label::AbstractString
    @compat range::Union{Empty, Range}
    value::T
end

function empty(t::Type)
    if is(t, Number) zero(t)
    elseif is(t, AbstractString) ""
    end
end

function Textbox(; label="",
                 value=utf8(""),
                 # Allow unicode characters even if initiated with ASCII
                 typ=typeof(value),
                 range=nothing,
                 signal=Signal(typ, value))
    if isa(value, AbstractString) && range != nothing
        throw(ArgumentError(
               "You cannot set a range on a string textbox"
             ))
    end
    Textbox{typ}(signal, label, range, value)
end

textbox(;kwargs...) = Textbox(;kwargs...)

"""
    textbox(value=""; label="", typ=typeof(value), range=nothing, signal)

Create a box for entering text. `value` is the starting value; if you
don't want to provide an initial value, you can constrain the type
with `typ`. Optionally provide a `label`, specify the allowed range
(e.g., `-10.0:10.0`) for numeric entries, and/or provide the
(Reactive.jl) `signal` coupled to this text box.
"""
textbox(val; kwargs...) =
    Textbox(value=val; kwargs...)
textbox(val::AbstractString; kwargs...) =
    Textbox(value=utf8(val); kwargs...)

parse_msg{T<:Number}(w::Textbox{T}, val::AbstractString) = parse_msg(w, parse(T, val))
function parse_msg{T<:Number}(w::Textbox{T}, val::Number)
    v = convert(T, val)
    if isa(w.range, Range)
        # force value to stay in range
        v = max(first(w.range),
                min(last(w.range), v))
    end
    v
end

######################### Textarea ###########################

type Textarea{AbstractString} <: InputWidget{AbstractString}
    signal::Signal{AbstractString}
    label::AbstractString
    value::AbstractString
end

textarea(args...) = Textarea(args...)

textarea(; label="",
         value="",
         signal=Signal(value)) =
    Textarea(signal, label, value)

"""
    textarea(value=""; label="", signal)

Creates an extended text-entry area. Optionally provide a `label`
and/or the (Reactive.jl) `signal` associated with this widget. The
`signal` updates when you type.
"""
textarea(val; kwargs...) =
    textarea(value=val; kwargs...)

##################### SelectionWidgets ######################

immutable OptionDict
    keys::Vector
    dict::Dict
end

Base.getindex(x::OptionDict, y) = getindex(x.dict, y)
Base.haskey(x::OptionDict, y) = haskey(x.dict, y)
Base.keys(x::OptionDict) = x.keys
Base.values(x::OptionDict) = [x.dict[k] for k in keys(x)]
function Base.setindex!(x::OptionDict, v, k)
    if !haskey(x.dict, k)
        push!(x.keys, k)
    end
    x.dict[k] = v
    v
end
type Options{view, T} <: InputWidget{T}
    signal::Signal
    label::AbstractString
    value::T
    value_label::AbstractString
    options::OptionDict
    icons::AbstractArray
    tooltips::AbstractArray
end

Options(view::Symbol, options::OptionDict;
        label = "",
        value_label=first(options.keys),
        value=options[value_label],
        icons=[],
        tooltips=[],
        typ=typeof(value),
        signal=Signal(value)) =
            Options{view, typ}(signal, label, value, value_label, options, icons, tooltips)

addoption(opts, v::NTuple{2}) = opts[string(v[1])] = v[2]
addoption(opts, v) = opts[string(v)] = v
function Options(view::Symbol,
                    options::AbstractArray;
                    kwargs...)
    opts = OptionDict(Any[], Dict())
    for v in options
        addoption(opts, v)
    end
    Options(view, opts; kwargs...)
end

function Options(view::Symbol,
                    options::Associative;
                    kwargs...)
    opts = OptionDict(Any[], Dict())
    for (k, v) in options
        opts[string(k)] = v
    end
    Options(view, opts; kwargs...)
end

"""
    dropdown(choices; label="", value, typ, icons, tooltips, signal)

Create a "dropdown" widget. `choices` can be a vector of
options. Optionally specify the starting `value` (defaults to the
first choice), the `typ` of elements in `choices`, supply custom
`icons`, provide `tooltips`, and/or specify the (Reactive.jl) `signal`
coupled to this widget.

# Examples

    a = dropdown(["one", "two", "three"])

To link a callback to the dropdown, use

    f = dropdown(["turn red"=>colorize_red, "turn green"=>colorize_green])
    map(g->g(image), signal(f))
"""
dropdown(opts; kwargs...) =
    Options(:Dropdown, opts; kwargs...)

"""
radiobuttons: see the help for `dropdown`
"""
radiobuttons(opts; kwargs...) =
    Options(:RadioButtons, opts; kwargs...)

select(opts; kwargs...) =
    Options(:Select, opts; kwargs...)

"""
togglebuttons: see the help for `dropdown`
"""
togglebuttons(opts; kwargs...) =
    Options(:ToggleButtons, opts; kwargs...)

### Output Widgets

export Latex, Progress

Base.@deprecate html(value; label="")  HTML(value)

type Latex <: Widget
    label::AbstractString
    value::AbstractString
end
latex(label, value::AbstractString) = Latex(label, value)
latex(value::AbstractString; label="") = Latex(label, value)
latex(value; label="") = Latex(label, mimewritable("application/x-latex", value) ? stringmime("application/x-latex", value) : stringmime("text/latex", value))

## # assume we already have Latex
## writemime(io::IO, m::MIME{symbol("application/x-latex")}, l::Latex) =
##     write(io, l.value)

type Progress <: Widget
    label::AbstractString
    value::Int
    range::Range
end

progress(args...) = Progress(args...)
progress(;label="", value=0, range=0:100) =
    Progress(label, value, range)

# Make a widget out of a domain
widget(x::Signal, label="") = x
widget(x::Widget, label="") = x
widget(x::Range, label="") = slider(x, label=label)
widget(x::AbstractVector, label="") = togglebuttons(x, label=label)
widget(x::Associative, label="") = togglebuttons(x, label=label)
widget(x::Bool, label="") = checkbox(x, label=label)
widget(x::AbstractString, label="") = textbox(x, label=label, typ=AbstractString)
widget{T <: Number}(x::T, label="") = textbox(typ=T, value=x, label=label)


################################################################################
# Fundamental "neural" data types
################################################################################
struct Spike
	n::UInt64
	t::Float64
end
Base.show(io::IO, s::Spike) = print(io, "(Neuron $(s.n): $(s.t) s)")

struct Synapse
	strength::Float64
	source::UInt64
	dest::UInt64
	delay::UInt16 	# in number of samples
end
Base.show(io::IO, syn::Synapse) = print(io, "(Source: $(syn.source), Destination: $(syn.dest), Delay: $(syn.delay), Strength: $(syn.strength))")

abstract type Neuron end

################################################################################
# Izhikevich neuron
################################################################################
struct SimpleNeuron <: Neuron
	a::Float64
	b::Float64
	c::Float64
	d::Float64
end

struct SimpleNeuronType{T} end
SimpleNeuronType(s::Symbol) = SimpleNeuronType{s}()
SimpleNeuronType(s::AbstractString) = SimpleNeuronType{Symbol(s)}()

SimpleNeuron(s::Symbol) = SimpleNeuron(SimpleNeuronType{Symbol(s)}())
SimpleNeuron(s::AbstractString) = SimpleNeuron(SimpleNeuronType(s))

# Regular Spiking (Exc)
SimpleNeuron(::SimpleNeuronType{:rs}) = SimpleNeuron(0.02, 0.2, -65.0, 8.0)
SimpleNeuron(::SimpleNeuronType{:RS}) = SimpleNeuron(0.02, 0.2, -65.0, 8.0)
# Intrinsically Bursting (Exc)
SimpleNeuron(::SimpleNeuronType{:ib}) = SimpleNeuron(0.02, 0.2, -55.0, 4.0)
SimpleNeuron(::SimpleNeuronType{:IB}) = SimpleNeuron(0.02, 0.2, -55.0, 4.0)
# Chattering (Exc)
SimpleNeuron(::SimpleNeuronType{:ch}) = SimpleNeuron(0.02, 0.2, -50.0, 2.0)
SimpleNeuron(::SimpleNeuronType{:CH}) = SimpleNeuron(0.02, 0.2, -50.0, 2.0)
# Fast Spiking (Inh)
SimpleNeuron(::SimpleNeuronType{:fs}) = SimpleNeuron(0.1, 0.2, -65.0, 2.0)
SimpleNeuron(::SimpleNeuronType{:FS}) = SimpleNeuron(0.1, 0.2, -65.0, 2.0)
# Low-Threshold Spiking (Inh)
SimpleNeuron(::SimpleNeuronType{:lts}) = SimpleNeuron(0.02, 0.25, -65.0, 2.0)
SimpleNeuron(::SimpleNeuronType{:LTS}) = SimpleNeuron(0.02, 0.25, -65.0, 2.0)
# Thalamo-Cortical
SimpleNeuron(::SimpleNeuronType{:tc}) = SimpleNeuron(0.02, 0.25, -65.0, 0.05)
SimpleNeuron(::SimpleNeuronType{:TC}) = SimpleNeuron(0.02, 0.25, -65.0, 0.05)
# Resonator
SimpleNeuron(::SimpleNeuronType{:rz}) = SimpleNeuron(0.1, 0.26, -65.0, 2.0)
SimpleNeuron(::SimpleNeuronType{:RZ}) = SimpleNeuron(0.1, 0.26, -65.0, 2.0)

# Random Excitatory
SimpleNeuron(::SimpleNeuronType{:exc}) = begin
	c = rand()^2.0
	SimpleNeuron(0.02, 0.2, -65.0+15.0c, 8.0-6.0c)
end
SimpleNeuron(::SimpleNeuronType{:EXC}) = SimpleNeuron(SimpleNeuronType(:exc))
# Random Inhibitory
SimpleNeuron(::SimpleNeuronType{:inh}) = begin
	r = rand()
	SimpleNeuron(0.02+0.08r, 0.25-0.05r, -65.0, 2.0)
end
SimpleNeuron(::SimpleNeuronType{:INH}) = SimpleNeuron(SimpleNeuronType(:inh))

################################################################################
# Gerstner neuron
################################################################################
mutable struct SRMNeuron <: Neuron
	u::Float64
	u_max::Float64
	u_rest::Float64
	Θ::Float64
	τ_refr::Float64
	τ_memb::Float64
	t_lastevent::Float64
	t_lastspike::Float64
	ϵ_last::Float64
end

SRMNeuron() = SRMNeuron(-65.0, 8.0, -65.0, -50.0, 7.0/1000.0, 3.0/1000.0,
						0.0, 0.0, 0.0)

SRMNeuron(τ_membrane) = SRMNeuron(-65.0, 8.0, -65.0, -50.0, 7.0/1000.0, τ_membrane,
								  0.0, 0.0, 0.0)

######################################################################
# Delay lines
######################################################################

mutable struct DelayLine
	numstored::Int64
	delaylen::Int64
	counts::Vector{Int64}
end

DelayLine(delaylen::Int64, maxevents::Int64) =
	DelayLine(0, delaylen, zeros(Int64, maxevents))

DelayLine(delaylen::Int64) = DelayLine(0, delaylen, zeros(Int64, 64))

DelayLine(numstored::Int64, delaylen::Int64, maxevents::Int64) =
	DelayLine(numstored, delaylen, zeros(Int64, maxevents))

################################################################################
# Types for managing experiments
################################################################################
struct ModelParams
	fs::Float64

	# Graph (revise later) -- now specified directly, but still used here
	num_neurons::UInt64
	p_contact::Float64
	p_exc::Float64
	maxdelay::Float64 # ms

	# STDP
	synmax::Float64
	tau_pre::Float64
	tau_post::Float64
	a_pre::Float64
	a_post::Float64
end
ModelParams() = ModelParams(2000.0, 1000, 0.1, 0.8, 20.0, 10.0,
							0.02, 0.02, 1.20, 1.0)

struct TrialParams
	dur::Float64
	lambda::Float64
	randspikesize::Float64
	randinput::Bool
	inhibition::Bool
    stdp::Bool
	inputmode::UInt64
	multiinputmode::UInt64
	inputweight::Float64
	recordstart::Float64
	recordstop::Float64
	lambdainput::Float64
	inputrefractorytime::Float64
end

TrialParams() = TrialParams(1.0, 3.0, 20.0, 1, 1, 1, 1, 1, 20.0, 0.0, 1.0, 0.5, 0.0)

function Base.show(io::IO, tp::TrialParams)
	println(io, "\tInput mode: $(tp.inputmode == 1 ? "periodic" : "poissonian")")
	println(io, "\tInput timing density (if random): $(tp.lambdainput) (1/s)")
	println(io, "\tInput size: $(tp.inputweight)")
	println(io, "\tDuration: $(tp.dur) (s)")
	println(io, "\tRandom input: $(tp.randinput)")
	println(io, "\tRand spike density (λ): $(tp.lambda) (1/s)")
	println(io, "\tRandom spike size: $(tp.randspikesize)")
	println(io, "\tInhibition: $(tp.inhibition)")
	println(io, "\tSTDP: $(tp.stdp)")
	println(io, "\tRecord start: $(tp.recordstart)")
	println(io, "\tRecord stop: $(tp.recordstop)")
end

struct Experiment
	name::String
	mp::ModelParams
	tp::TrialParams
	input::Array{Spike,1}
end

struct Model
	params::ModelParams
	delgraph::Array{Int64,2}
	syngraph::Array{Float64,2}
	neurons::Array{SimpleNeuron,1}
end

struct ExperimentOutput
	modelname::AbstractString
	trialname::AbstractString
	tp::TrialParams
	input::Array{Array{Spike,1},1}
	output::Array{Spike,1}
	inputtimes::Array{Float64,1}
	inputids::Array{Int64,1}
	synapses::Array{Synapse,1}
end

function Base.show(io::IO, exp::ExperimentOutput)
	println(IO, "Model Name: $(exp.modelname)")
	println(IO, "Trial Name: $(exp.trialname)")
	println(IO, "Trial Parameters")
	Base.show(io, exp.tp)
end

struct ExperimentType{T} end

ExperimentType(s::AbstractString) = ExperimentType{Symbol(s)}()

################################################################################
# Delay learning types
################################################################################
struct CategoricalSpiker
	dls::Array{DelayLine,1}
	neuron::SRMNeuron
	weight::Float64
end

CategoricalSpiker(inputs::T, possibledelays::T, weight;
				  τ_membrane=3.0/1000.0,
				  maxevents=64) where T <: Union{Array{Int64,1}, UnitRange{Int64}} = begin
	dls = [DelayLine(rand(possibledelays), maxevents) for _ ∈ inputs]
	neuron = SRMNeuron(τ_membrane)
	CategoricalSpiker(dls, neuron, weight)
end


using JLD2
using Statistics

mutable struct AutostepStepSize <: LoggerKeyData
    step_sizes::Array{Any}
    log_interval::Int

    function AutostepStepSize(logger_init_info)
        num_logged_steps = fld(logger_init_info[LoggerInitKey.TOTAL_STEPS], logger_init_info[LoggerInitKey.INTERVAL])
        println("The AutostepStepSize logger might be pretty inefficient and should probably only be used for Auto debug. If you are planning on running a sweep, it might be worth not logging step sizes.")
        new([], logger_init_info[LoggerInitKey.INTERVAL])
    end
end

function lg_step!(self::AutostepStepSize, env, agent, s, a, s_next, r, is_terminal, cur_step_in_episode, cur_step_total)
    if rem(cur_step_total, self.log_interval) == 0
        ind = fld(cur_step_total, self.log_interval)
        demon_lu = update(agent.demon_learner)
        opt = demon_lu.opt
        @assert demon_lu.opt isa Auto "Demon Optimizer is not Auto! Please set it to Auto or remove LoggerKey.AUTOSTEP_STEPSIZE from logger keys"

        step_size = []
        for k in keys(opt.α)
            α = opt.α[k]
            push!(step_size, copy(α))
        end
        push!(self.step_sizes, step_size)
    end
end

function lg_episode_end!(self::AutostepStepSize, cur_step_in_episode, cur_step_total)
end

function save_log(self::AutostepStepSize, save_dict::Dict)
    save_dict[:autostep_stepsize] = self.step_sizes
end


# keyboard & mouse callbacks for controls
const mov_forward = Ref(false)
const mov_left = Ref(false)
const mov_right = Ref(false)
const mov_back = Ref(false)
const mov_up = Ref(false)
const mov_down = Ref(false)
const mov_boost = Ref(false)

function key_cb_fun(window, key, _, action)
    if key == MiniFB.KB_KEY_W # up
        mov_forward[] = action
    elseif key == MiniFB.KB_KEY_A # left
        mov_left[] = action
    elseif key == MiniFB.KB_KEY_D # right
        mov_right[] = action
    elseif key == MiniFB.KB_KEY_S # down
        mov_back[] = action
    elseif key == MiniFB.KB_KEY_SPACE # space
        mov_up[] = action
    elseif key == MiniFB.KB_KEY_LEFT_CONTROL # l-ctrl
        mov_down[] = action
    elseif key == MiniFB.KB_KEY_LEFT_SHIFT # l-shift
        mov_boost[] = action
    elseif key == MiniFB.KB_KEY_ESCAPE
        mfb_close(window)
    end
    return nothing
end

const mouse_pos = Int32[0,0]
const mouse_mov = Int32[0,0]

function mouse_cb_fun(window, x, y)
    mouse_mov .= (x,y) .- mouse_pos
    mouse_pos .= (x,y)
    return nothing
end

function compute_movement!(θ)
    # mouse
    dθ = -mouse_mov[1]*1f-2
    fill!(mouse_mov, zero(Int32))
    # keyboard; signs empirically adjusted...
    sθ, cθ = sincos(θ)
    dx = (mov_right[]-mov_left[])*cθ - (mov_forward[]-mov_back[])*sθ
    dy = -(mov_right[]-mov_left[])*sθ - (mov_forward[]-mov_back[])*cθ
    dz = (mov_up[]-mov_down[])
    n = dx>0 ? 1f0/hypot(dx,dy) : 1f0
    return dx*n*(1 + mov_boost[]), dy*n*(1 + mov_boost[]), dz, dθ
end


@bsr_exe zgenconf
@bsr_exe zgenterm

@bsr_exe bsr_breit
@bsr_mpi_exe bsr_breit_mpi
@bsr_exe ci_bnk
@bsr_exe bsr_mat
@bsr_mpi_exe bsr_mat_mpi
@bsr_exe bsr_hd

@bsr_exe mult
@bsr_exe bsr_dmat
@bsr_exe bsr_phot
@bsr_mpi_exe bsr_phot_mpi

function dipole(cfg1, cfg2, t1, t2, trans="E1", args...; kwargs...)
    mult(cfg1, cfg2, trans)
    bsr_dmat(cfg1, cfg2, t1, t2, args...; kwargs...)
end

function photo(photo_inp::Matrix, klsp::Integer)
    open("bsr_phot.inp", "w") do file
        write(file, "-------\n")
        for i = 1:size(photo_inp,1)
            if photo_inp[i,1] != ""
                write(file,
                      string(photo_inp[i,1]), " = ",
                      string(photo_inp[i,2]), "\n")
            end
        end
        write(file, "-------\n")
    end
    debug("bsr_phot.inp:\n$(photo_inp)")

    bsr_phot(klsp = klsp)
end

export dipole, photo


## Copyright (c) 2015, Júlio Hoffimann Mendes <juliohm@stanford.edu>
##
## Permission to use, copy, modify, and/or distribute this software for any
## purpose with or without fee is hereby granted, provided that the above
## copyright notice and this permission notice appear in all copies.
##
## THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
## WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
## MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
## ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
## WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
## ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
## OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

@userplot VoxelReusePlot

@recipe function f(vr::VoxelReusePlot;
        tmin=nothing, tmax=nothing, nreal=10,
        gpu=false, soft=[], hard=HardData(), tol=.1,
        overlapx=1/6, overlapy=1/6, overlapz=1/6,
        cut=:boykov, simplex=false, threads=CPU_PHYSICAL_CORES)
  # get input image
  img = vr.args[1]

  @assert ndims(img) == 3 "image is not 3D (add ghost dimension for 2D)"

  # image extent
  extent = size(img)
  idx = [extent...] .> 1

  # default support
  tmin == nothing && (tmin = 7)
  tmax == nothing && (tmax = min(100, minimum(extent[idx])))

  @assert tmin > 0 "tmin must be positive"
  @assert tmin < tmax "tmin must be smaller than tmax"

  # save support as an array
  ts = collect(tmin:tmax)

  # -- compute voxel reuse
  extent = size(img)
  idx = [extent...] .> 1

  p = Progress(length(ts), color=:black)
  μs = Float64[]; σs = Float64[]
  for T in ts
    tplconfig = [1,1,1]
    tplconfig[idx] = T
    μ, σ = voxelreuse(img, tplconfig...,
            nreal=nreal, gpu=gpu, soft=soft, hard=hard,
			overlapx=overlapx, overlapy=overlapy, overlapz=overlapz,
			cut=cut, simplex=simplex, threads=threads)
    push!(μs, μ); push!(σs, σ)

    next!(p)
  end
  # --

  # highlight the optimum template range
  @series begin
    rank = sortperm(μs, rev=true)
    best = ts[rank[1:min(5,length(ts))]]
    xmin, xmax = minimum(best), maximum(best)

    reference = minimum(μs - σs)
    ymin = max(0., reference - .05)
    ymax = ymin + .008

    tplrange = [xmin, xmax]

    seriestype := :shape
    linewidth := 0
    fillalpha := .5
    label --> "Optimum range: $tplrange"

    [xmin, xmax, xmax, xmin], [ymin, ymin, ymax, ymax]
  end

  seriestype := :path
  primary := false
  ribbon := σs
  fillalpha := .5
  xlim := (0, tmax)
  xlabel --> "Template size"
  ylabel --> "Voxel reuse"

  ts, μs
end


import Base: +, -

const genericwrappers = (
    :LowerTriangular,
    :UpperTriangular,
    :Hermitian,
    :Symmetric
)

const unittriangularwrappers = (
    (:UnitUpperTriangular, :UpperTriangular),
    (:UnitLowerTriangular, :LowerTriangular)
)

function kernel_generic(ctx, B, J, min_size)
    lin_idx = linear_index(ctx)
    lin_idx > min_size && return nothing
    @inbounds diag_idx = diagind(B)[lin_idx]
    @inbounds B[diag_idx] += J
    return nothing
end

function kernel_unittriangular(ctx, B, J, diagonal_val, min_size)
    lin_idx = linear_index(ctx)
    lin_idx > min_size && return nothing
    @inbounds diag_idx = diagind(B)[lin_idx]
    @inbounds B[diag_idx] = diagonal_val + J
    return nothing
end

for (t1, t2) in unittriangularwrappers
    @eval begin
        function (+)(A::$t1{T, <:AbstractGPUMatrix}, J::UniformScaling) where T
            B = similar(parent(A), typeof(oneunit(T) + J))
            copyto!(B, parent(A))
            min_size = minimum(size(B))
            gpu_call(kernel_unittriangular, B, J, one(eltype(B)), min_size; elements=min_size)
            return $t2(B)
        end

        function (-)(J::UniformScaling, A::$t1{T, <:AbstractGPUMatrix}) where T
            B = similar(parent(A), typeof(J - oneunit(T)))
            B .= .- parent(A)
            min_size = minimum(size(B))
            gpu_call(kernel_unittriangular, B, J, -one(eltype(B)), min_size; elements=min_size)
            return $t2(B)
        end
    end
end

for t in genericwrappers
    @eval begin
        function (+)(A::$t{T, <:AbstractGPUMatrix}, J::UniformScaling) where T
            B = similar(parent(A), typeof(oneunit(T) + J))
            copyto!(B, parent(A))
            min_size = minimum(size(B))
            gpu_call(kernel_generic, B, J, min_size; elements=min_size)
            return $t(B)
        end

        function (-)(J::UniformScaling, A::$t{T, <:AbstractGPUMatrix}) where T
            B = similar(parent(A), typeof(J - oneunit(T)))
            B .= .- parent(A)
            min_size = minimum(size(B))
            gpu_call(kernel_generic, B, J, min_size; elements=min_size)
            return $t(B)
        end
    end
end

# Breaking Hermiticity when adding a complex value to the diagonal
function (+)(A::Hermitian{T,<:AbstractGPUMatrix}, J::UniformScaling{<:Complex}) where T
    B = similar(parent(A), typeof(oneunit(T) + J))
    copyto!(B, parent(A))
    min_size = minimum(size(B))
    gpu_call(kernel_generic, B, J, min_size; elements=min_size)
    return B
end

function (-)(J::UniformScaling{<:Complex}, A::Hermitian{T,<:AbstractGPUMatrix}) where T
    B = similar(parent(A), typeof(J - oneunit(T)))
    B .= .-parent(A)
    min_size = minimum(size(B))
    gpu_call(kernel_generic, B, J, min_size; elements=min_size)
    return B
end

# Finally the generic matrix version
function (+)(A::AbstractGPUMatrix{T}, J::UniformScaling) where T
    B = similar(A, typeof(oneunit(T) + J))
    copyto!(B, A)
    min_size = minimum(size(B))
    gpu_call(kernel_generic, B, J, min_size; elements=min_size)
    return B
end

function (-)(J::UniformScaling, A::AbstractGPUMatrix{T}) where T
    B = similar(A, typeof(J - oneunit(T)))
    B .= .-A
    min_size = minimum(size(B))
    gpu_call(kernel_generic, B, J, min_size; elements=min_size)
    return B
end


using DynamicPPL
using DynamicPPL: apply_dotted, getargs_dottilde, getargs_tilde

using Test

@testset "apply_dotted" begin
    # Some things that are not expressions.
    @test apply_dotted(:x) === :x
    @test apply_dotted(1.0) === 1.0
    @test apply_dotted([1.0, 2.0, 4.0]) == [1.0, 2.0, 4.0]

    # Some expressions.
    @test apply_dotted(:(x ~ Normal(μ, σ))) == :(x ~ Normal(μ, σ))
    @test apply_dotted(:((.~)(x, Normal(μ, σ)))) == :((.~)(x, Normal(μ, σ)))
    @test apply_dotted(:((~).(x, Normal(μ, σ)))) == :((~).(x, Normal(μ, σ)))
    @test apply_dotted(:(@. x ~ Normal(μ, σ))) == :((~).(x, Normal.(μ, σ)))
    @test apply_dotted(:(@. x ~ Normal(μ, $(Expr(:$, :(sqrt(v))))))) ==
        :((~).(x, Normal.(μ, sqrt(v))))
    @test apply_dotted(:(@~ Normal.(μ, σ))) == :(@~ Normal.(μ, σ))
end

@testset "getargs_dottilde" begin
    # Some things that are not expressions.
    @test getargs_dottilde(:x) === nothing
    @test getargs_dottilde(1.0) === nothing
    @test getargs_dottilde([1.0, 2.0, 4.0]) === nothing

    # Some expressions.
    @test getargs_dottilde(:(x ~ Normal(μ, σ))) === nothing
    @test getargs_dottilde(:((.~)(x, Normal(μ, σ)))) == (:x, :(Normal(μ, σ)))
    @test getargs_dottilde(:((~).(x, Normal(μ, σ)))) == (:x, :(Normal(μ, σ)))
    @test getargs_dottilde(:(@. x ~ Normal(μ, σ))) === nothing
    @test getargs_dottilde(:(@. x ~ Normal(μ, $(Expr(:$, :(sqrt(v))))))) === nothing
    @test getargs_dottilde(:(@~ Normal.(μ, σ))) === nothing
end

@testset "getargs_tilde" begin
    # Some things that are not expressions.
    @test getargs_tilde(:x) === nothing
    @test getargs_tilde(1.0) === nothing
    @test getargs_tilde([1.0, 2.0, 4.0]) === nothing

    # Some expressions.
    @test getargs_tilde(:(x ~ Normal(μ, σ))) == (:x, :(Normal(μ, σ)))
    @test getargs_tilde(:((.~)(x, Normal(μ, σ)))) === nothing
    @test getargs_tilde(:((~).(x, Normal(μ, σ)))) === nothing
    @test getargs_tilde(:(@. x ~ Normal(μ, σ))) === nothing
    @test getargs_tilde(:(@. x ~ Normal(μ, $(Expr(:$, :(sqrt(v))))))) === nothing
    @test getargs_tilde(:(@~ Normal.(μ, σ))) === nothing
end


@testset "learner" begin
    nfs = [8,16,32,32]
    model = get_model(nfs)
    opti = Descent(1e-2)
    loss(pred,y) = logitcrossentropy(pred,y)
    learn = Learner(model,opti,loss,data)

    @test learn.callbaks[1].order == 0
end


using .DifferentialEquations
const DE = DifferentialEquations

using HybridSystems: states

# extend the solve API for initial-value problems

# =====================================
# Continuous system
# =====================================

function _solve_ensemble(ivp::InitialValueProblem, args...;
                         trajectories_alg=DE.Tsit5(),
                         ensemble_alg=DE.EnsembleThreads(),
                         inplace=true,
                         initial_states=nothing,
                         kwargs...)

    # get problem's vector field
    if inplace
        field = inplace_field!(ivp)
    else
        field = outofplace_field(ivp)
    end

    # get time span
    dt = _get_tspan(args...; kwargs...)
    tspan = (inf(dt), sup(dt))

    if isnothing(initial_states)
        # sample initial states
        X0 = initial_state(ivp)
        trajectories = get(kwargs, :trajectories, 100)
        initial_states = _sample_initial(X0, trajectories; kwargs...)
        # number of trajectories may increase if vertices got included
        trajectories = length(initial_states)
    else
        trajectories = length(initial_states)
    end

    # formulate ensemble ODE problem
    ensemble_prob = ODEProblem(field, first(initial_states), tspan)
    _prob_func(prob, i, repeat) = remake(prob, u0 = initial_states[i])

    # choose tolerances
    reltol = get(kwargs, :reltol, 1e-3)
    abstol = get(kwargs, :abstol, 1e-6)

    callback = get(kwargs, :callback, nothing)
    dtmax = get(kwargs, :dtmax, Inf)

    ensemble_prob = EnsembleProblem(ensemble_prob, prob_func = _prob_func)

    if isnothing(callback)
        result = DE.solve(ensemble_prob, trajectories_alg, ensemble_alg;
                          trajectories=trajectories, reltol=reltol,
                          abstol=abstol, dtmax=dtmax)
    else
        result = DE.solve(ensemble_prob, trajectories_alg, ensemble_alg;
                          trajectories=trajectories, reltol=reltol,
                          abstol=abstol, dtmax=dtmax, callback=callback)
    end
    return result
end

function _sample_initial(X0, trajectories; kwargs...)
    sampler = get(kwargs, :sampler, LazySets._default_sampler(X0))
    rng = get(kwargs, :rng, LazySets.GLOBAL_RNG)
    seed = get(kwargs, :seed, nothing)
    include_vertices = get(kwargs, :include_vertices, false)
    return sample(X0, trajectories; sampler=sampler, rng=rng,
                  seed=seed, include_vertices=include_vertices)
end

# =====================================
# Hybrid system
# =====================================

function _solve_ensemble(ivp::InitialValueProblem{<:AbstractHybridSystem},
                         args...; kwargs...)
    H = system(ivp)

    kwargs_sim = copy(kwargs)
    if :T in keys(kwargs_sim)
        # remove `T` because it is not allowed to define several time options
        pop!(kwargs_sim, :T)
    end

    time_span = _get_tspan(args...; kwargs...)
    t0_g = tstart(time_span)
    T = tend(time_span)

    termination_action = (integrator) -> terminate!(integrator)
    use_discrete_callback = get(kwargs, :use_discrete_callback, false)
    dtmax = get(kwargs, :dtmax, Inf)
    if use_discrete_callback && isinf(dtmax)
        # use a maximum time step when using discrete callbacks
        dtmax = DTMAX_SIM_DEFAULT
        kwargs_sim[:dtmax] = dtmax
    end

    jump_probability = get(kwargs, :jump_probability, 0.9)
    max_jumps = get(kwargs, :max_jumps, 100)

    # hybrid loop
    all_trajectories = []
    for initial_state in _sample_initial(ivp; kwargs...)
        loc, x0 = initial_state
        t0 = t0_g
        jump_count = 0
        trajectory_chain = []
        while true
            S_loc = mode(H, loc)
            ivp_loc = IVP(S_loc, x0)
            I⁻ = stateset(S_loc)

            # obtain trajectory in current mode
            if use_discrete_callback
                # use a discrete callback function based on membership
                condition = (u, t, integrator) -> u ∉ I⁻
                callback = DiscreteCallback(condition, termination_action)
            else
                # use a continuous callback function based on membership
                condition = (u, t, integrator) -> u ∉ I⁻ ? -1 : 1
                callback = ContinuousCallback(condition, termination_action)
            end
            trajectory = _solve_ensemble(ivp_loc, args...; initial_states=[x0],
                                         callback=callback, tspan=t0 .. T,
                                         kwargs_sim...)[1]

            # remove states from trajectory if they violate invariant
            # observation: this only concerns the last two steps
            if use_discrete_callback
                start_idx = max(1, length(trajectory.u) - 1)
                for idx in start_idx:length(trajectory.u)
                    if trajectory.u[idx] ∉ I⁻
                        indices_to_remove = idx:length(trajectory.u)
                        deleteat!(trajectory.u, indices_to_remove)
                        deleteat!(trajectory.t, indices_to_remove)
                        deleteat!(trajectory.k, indices_to_remove)
                        break
                    end
                end
            end

            # decide whether another jump should be attempted
            if jump_count > max_jumps
                # exceeded the allowed number of jumps
                try_jump = false
            elseif trajectory.t[end] >= T
                # time horizon reached → potentially do not take another jump
                try_jump = rand() <= jump_probability
            else
                try_jump = true
            end

            if try_jump
                # collect potential successors
                successors = []
                for t in out_transitions(H, loc)
                    G = guard(H, t)
                    asgn = resetmap(H, t)
                    loc′ = target(H, t)
                    I⁺ = stateset(H, loc′)

                    # find simulation steps that are enabled
                    # collect pairs (i, t, y) of indices i when transition t is
                    # enabled and leads to state y
                    for (i, xi) in enumerate(trajectory.u)
                        # check guard intersection
                        xi ∉ G && continue

                        # apply assignment
                        yi = MathematicalSystems.apply(asgn, xi)

                        # check target invariant
                        yi ∉ I⁺ && continue

                        # can take the transition
                        # store index, transition, and state
                        storage = (i, t, yi)
                        push!(successors, storage)
                    end
                end

                if isempty(successors)
                    jump_successful = false
                else
                    # choose a random successor
                    idx, t, y = rand(successors)
                    indices_to_remove = idx+1:length(trajectory.u)
                    deleteat!(trajectory.u, indices_to_remove)
                    deleteat!(trajectory.t, indices_to_remove)
                    deleteat!(trajectory.k, indices_to_remove)
                    loc′ = target(H, t)
                    push!(trajectory.u, y)
                    push!(trajectory.t, trajectory.t[end])
                    push!(trajectory.k, trajectory.k[end])
                    jump_successful = true
                end
            else
                jump_successful = false
            end

            # append new trajectory suffix to old trajectory prefix
            push!(trajectory_chain, trajectory)

            # test for termination
            if !jump_successful
                push!(all_trajectories, _merge_trajectory_chain(trajectory_chain))
                break
            end

            # continue previous simulation
            jump_count += 1
            loc = loc′
            x0 = trajectory.u[end]
            t0 = trajectory.t[end]
        end
    end
    return all_trajectories
end

# sample initial states of hybrid system given a list of pairs (loc, X0)
function _sample_initial(ivp::IVP{<:AbstractHybridSystem,
                                  <:Vector{<:Tuple{Integer, AdmissibleSet}}};
                         kwargs...)
    H = system(ivp)
    X0 = initial_state(ivp)

    # sample initial states from all possible initial regions
    trajectories = get(kwargs, :trajectories, 100)
    all_samples = []
    for (loc, X0_loc) in X0
        inv = stateset(mode(H, loc))
        X0_loc = X0_loc ∩ inv
        if isempty(X0_loc)
            continue
        end
        for x0 in _sample_initial(X0_loc, trajectories; kwargs...)
            push!(all_samples, (loc, x0))
        end
    end

    # filter for correct number of trajectories
    include_vertices = get(kwargs, :include_vertices, false)
    if !include_vertices
        # with k initial regions, we collected up to `k * trajectories` many
        # samples (some regions may be empty), so we reduce to a random
        # collection of `trajectories` samples
        rng = get(kwargs, :rng, LazySets.GLOBAL_RNG)
        all_samples = LazySets.Random.shuffle!(rng, all_samples)[1:trajectories]
    end

    return all_samples
end

# sample initial states of hybrid system given a set X0
function _sample_initial(ivp::IVP{<:AbstractHybridSystem, <:AdmissibleSet};
                         kwargs...)
    H = system(ivp)
    X0 = initial_state(ivp)
    X0_distributed = [(loc, X0) for loc in states(H)]

    # filter invalid combinations (empty intersection with invariant)
    i = 1
    while i <= length(X0_distributed)
        loc, X0 = X0_distributed[i]
        inv = stateset(H, loc)
        if isdisjoint(inv, X0)
            deleteat!(X0_distributed, i)
        else
            i += 1
        end
    end

    return _sample_initial(IVP(H, X0_distributed))
end

# merge trajectory pieces into an ODESolution object
function _merge_trajectory_chain(trajectory_chain)
    prefix = trajectory_chain[1]
    @inbounds for i in 2:length(trajectory_chain)
        infix = trajectory_chain[i]
        append!(prefix.u, infix.u)
        append!(prefix.t, infix.t)
        append!(prefix.k, infix.k)
    end
    return prefix
end


using StingerWrapper
using BenchmarkTools
using GraphBenchmarks
import GraphBenchmarks: construct, preparebench, picksources

import StingerWrapper: bfs

export StingerGraph, SerialBFS, LevelSynchronousBFS, StingerBenchmarks

immutable StingerGraph <: GraphType
    s::Stinger
    nv::Int64
end

immutable StingerBenchmarks <: GraphBenchmarkSpec end

immutable SerialBFS <: AbstractBFS end
immutable LevelSynchronousBFS <: AbstractBFS end

function construct(t::Type{StingerGraph}, edges::Array{Int64, 2})
    s = Stinger()
    for i=1:size(edges,2)
        insert_edge!(s, 0, edges[1, i]-1, edges[2, i]-1, 0, 0)
    end
    StingerGraph(s, maximum(edges)) #max number of active vertices
end

function picksources(::StingerBenchmarks, nv::Int64)
    sources = collect(1:1000)
end

function preparebench(benchmark::GraphBenchmarkSpec, alg::SerialBFS, g::StingerGraph)
    sources = picksources(benchmark, g.nv) - 1
    bfsbench = @benchmarkable begin
        for src in $sources
            StingerWrapper.bfs($(g.s), src, $(g.nv))
        end
    end seconds=6000 samples=10
    bfsbench
end

function preparebench(benchmark::GraphBenchmarkSpec, alg::LevelSynchronousBFS, g::StingerGraph)
    sources = picksources(benchmark, g.nv) - 1
    bfsbench = @benchmarkable begin
        for src in $sources
            StingerWrapper.bfs(StingerWrapper.LevelSynchronous(), $(g.s), src, $(g.nv))
        end
    end seconds=6000 samples=10
    bfsbench
end

function bfsbenchsuite(T::Type{StingerGraph}, bench::GraphBenchmarkSpec, scale::Int, edgefactor::Int)
    srand(0)
    gen = Kronecker(scale, edgefactor)
    suite = BenchmarkGroup(["bfs", "stinger"])
    suite["serial"] = BenchmarkGroup(["serialbfs"])
    suite["serial"]["stingerserialbfs"] = preparebenchmark(bench, T, SerialBFS(), gen)
    suite["parallel"] = BenchmarkGroup(["parallelbfs"])
    suite["parallel"]["levelsyncbfs"] = preparebenchmark(bench, T, LevelSynchronousBFS(), gen)
    suite
end

function comparebfs(T::Type{StingerGraph}, bench::GraphBenchmarkSpec, scale::Int, edgefactor::Int)
    suite = bfsbenchsuite(T, bench, scale, edgefactor)
    run(suite, verbose=true)
end


include("impl.jl")

for (front_name, backend) in (
        :conv          => :_nnpack,
        :∇conv_data    => :_nnpack,
        :∇conv_filter  => :_nnpack,
    )
    @eval begin
        function $(Symbol("$(front_name)$(backend)!"))(
                        out::Array{T1,4}, in1::Array{T2,4}, in2::Array{T3,4},
                        cdims::ConvDims; kwargs...) where {T1, T2, T3}
            @warn "Automatically converting input tensor to Float32. This will have performance implications" maxlog=1
            # Output must of the same type as in the function signature
            T1.($(Symbol("$(front_name)$(backend)!"))(Float32.(out), Float32.(in1),
                                                      Float32.(in2), cdims; kwargs...))
        end
    end
end

function conv_nnpack(x::Array{T1, 4}, w::Array{T2, 4}, cdims::ConvDims; kwargs...) where {T1, T2}
    y = similar(x, output_size(cdims)..., channels_out(cdims), size(x, 4))
    return conv_nnpack!(y, x, w, cdims; kwargs...)
end

function ∇conv_data(dy::Array{T1, 4}, w::Array{T2, 4}, cdims::ConvDims; kwargs...) where {T1, T2}
    dx = similar(dy, input_size(cdims)..., channels_in(cdims), size(dy, 4))
    return ∇conv_data!(dx, dy, w, cdims; kwargs...)
end

function ∇conv_filter(x::Array{T1, 4}, dy::Array{T2, 4}, cdims::ConvDims; kwargs...) where {T1, T2}
    dw = similar(x, kernel_size(cdims)..., channels_in(cdims), channels_out(cdims))
    return ∇conv_filter!(dw, x, dy, cdims; kwargs...)
end

function maxpool_nnpack!(y::Array{T1, 4}, x::Array{T2, 4}, pdims::PoolDims;
                         kwargs...) where {T1, T2}
    @warn "Automatically converting input tensor to Float32. This will have performance implications" maxlog=1
    # We want the output to be of the same type as desired
    T1.(maxpool_nnpack!(Float32.(y), Float32.(x), pdims; kwargs...))
end

function maxpool_nnpack(x::Array{T, 4}, pdims::PoolDims; kwargs...) where {T}
    y = similar(x, output_size(pdims)..., channels_out(pdims), size(x, 4))
    return maxpool_nnpack!(y, x, pdims; kwargs...)
end

"""
    check_supported_operation(x::Array, pdims::PoolDims)

Returns `true` if nnpack supports the pooling operation for the given input.
"""
function check_supported_operation(x::Array{T, 4}, pdims::PoolDims{2, K, S, P, (1, 1)}) where {T, K, S, P}
    val = size(x)[1:2] .+ (P[1] + P[2], P[3] + P[4]) .- K
    return val .% S == (0, 0) ? true : false
end


#    Problem :
#    *********
#
#    The Boundary Value problem.
#    This is the nonlinear least-squares version without fixed variables.
#
#
#    Source:  problem 28 in
#    J.J. More', B.S. Garbow and K.E. Hillstrom,
#    "Testing Unconstrained Optimization Software",
#    ACM Transactions on Mathematical Software, vol. 7(1), pp. 17-41, 1981.
#
#    Implementation translated from:
#    See also Buckley#17 (p. 94):
#
#
#    SIF input: Ph. Toint, Dec 1989 and Nick Gould, Oct 1992.
#
#    classification SUR2-MN-V-0
#    The number of variables is N.
#
# Daniel Henderson, 08/2021

# f = x -> begin

# end

# g! = (g, x) -> begin
# 	n = lastindex(x)
# end

# fg! = (g, x) -> begin

# end

# init = (n::Int=5000) -> begin

# end

# @warn "FRUEROTH needs to be implemented"

# TestSet["FREUROTH"] = UncProgram("FREUROTH",  f, g!, fg!, init)


export ## Types
       ## Methods
       unitary_propagator

CompositeQSystem() = CompositeQSystem(QSystem[],
                                      Interaction[],
                                      ParametricInteraction[],
                                      Tuple{Vector{IndexSet},Vector{IndexSet},Vector{IndexSet}}[],
                                      Tuple{Vector{IndexSet},Vector{IndexSet},Vector{IndexSet}}[],
                                      Tuple{Vector{IndexSet},Vector{IndexSet},Vector{IndexSet}}[],
                                      Dissipation[])

function getindex(c::CompositeQSystem, key::AbstractString)
    for s in c.subSystems
        if label(s) == key
            return s
        end
    end
    throw(KeyError(key))
end

+(s1::QSystem, s2::QSystem) = (c = CompositeQSystem(); c += s1; c += s2; c)
+(s::QSystem, i::Interaction) = (c = CompositeQSystem(); c += s; c += i; c)

function +(c::CompositeQSystem, q::QSystem)
    append!(c.subSystems, [q])
    update_expansion_indices!(c)
    return c
end

function +(c::CompositeQSystem, i::Interaction)
    append!(c.interactions, [i])
    update_expansion_indices!(c)
    return c
end

function +(c::CompositeQSystem, pi::ParametricInteraction)
    append!(c.parametericInteractions, [pi])
    return c
end

function +(c::CompositeQSystem, d::Dissipation)
    append!(c.dissipators, [d])
    update_expansion_indices!(c)
    return c
end

function update_expansion_indices!(c::CompositeQSystem)
    ddims = [dims(c); dims(c)]
    subsystems = length(c.subSystems)

    c.subSystemExpansions = [(IndexSet[],IndexSet[],IndexSet[]) for _ = 1:length(c.subSystems)]
    for (ct, sys) in enumerate(c.subSystems)
        c.subSystemExpansions[ct] = (expand_indices([ct], dims(c)),            # operator
                                     expand_indices([ct], ddims),              # superoperator right
                                     expand_indices([ct.+subsystems], ddims))   # superoperator left
    end

    c.interactionExpansions = [(IndexSet[],IndexSet[],IndexSet[]) for _ = 1:length(c.interactions)]
    for (ct, i) in enumerate(c.interactions)
        pos_list = find_subsystem_pos(c,i)
        c.interactionExpansions[ct] = (expand_indices(pos_list, dims(c)), # operator
                                       expand_indices(pos_list, ddims),   # superoperator right
                                       expand_indices(map(x->x.+subsystems,pos_list), ddims)) #superoperator left
    end

    c.dissipatorExpansions = [(IndexSet[], IndexSet[], IndexSet[]) for _ = 1:length(c.dissipators)]
    for (ct, d) in enumerate(c.dissipators)
        subsys = find_subsystem_pos(c, d)
        # for efficiency, we need expansion for an operator acting on the left,
        # another for one acting on the right, and one for operators acting on both sides
        c.dissipatorExpansions[ct]= (expand_indices( [subsys.+subsystems;], ddims),  # left
                                     expand_indices( [subsys;], ddims),             # right
                                     expand_indices( [subsys; subsys.+subsystems], ddims)) # bilateral
    end
end

labels(c::CompositeQSystem) = [label(s) for s in c.subSystems]
dims(c::CompositeQSystem) = [dim(s) for s in c.subSystems]
dim(c::CompositeQSystem) = prod([dim(s) for s in c.subSystems])

function find_subsystem_pos(c::CompositeQSystem, s::QSystem)
    @assert s in c.subSystems "Oops! Subsystem not found."
    findall(in([s]), c.subSystems)
end

find_subsystem_pos(c::CompositeQSystem, i::Interaction) = [find_subsystem_pos(c, i.system1); find_subsystem_pos(c, i.system2)]
find_subsystem_pos(c::CompositeQSystem, i::FluxTransmon) = find_subsystem_pos(c, i.transmon)
find_subsystem_pos(c::CompositeQSystem, i::SemiClassicalDipole) = find_subsystem_pos(c, i.system2)
find_subsystem_pos(c::CompositeQSystem, i::RotatingSemiClassicalDipole) = find_subsystem_pos(c, i.system2)

function find_subsystem_pos(c::CompositeQSystem, d::Dissipation)
    @assert d in c.dissipators "Oops! Dissipator not found in composite system."
    findall(in([d.system]), c.subSystems)
end

function hamiltonian(c::CompositeQSystem, t::Float64=0.0)

    if length(c.subSystems) != 0
        #Initialize Hamiltonian
        Htot = zeros(ComplexF64, dim(c), dim(c))

        #Add in all the terms
        hamiltonian_add!(Htot, c, t)

        return Htot
    else
        error("No systems added to composite system yet.")
    end
end

function hamiltonian_add!(Ham::AbstractMatrix{T}, c::CompositeQSystem, t::Float64) where {T<:Number}
    #Fast system hamiltonian calculator with total Hamiltonian preallocated

    #Zero the Hamiltonian memory
    if issparse(Ham)
        # TODO: check: is it safe to assume that we will not make liouv denser and denser?
        rows,cols,_ = findnz(Ham)
        for i = 1:length(rows)
            Ham[rows[i],cols[i]] = 0.0
        end
    else
        fill!(Ham, zero(eltype(Ham)))
    end

    #Update the subsystems with the parameteric interactions
    for pi in c.parametericInteractions
        update_params(c, pi, t)
    end

    #Add together subsystem Hamiltonians
    for (subsys, expander) in zip(c.subSystems, c.subSystemExpansions)
        expand_add!(Ham, hamiltonian(subsys, t), expander[1])
    end

    #Add interactions
    for (subsys, expander) in zip(c.interactions, c.interactionExpansions)
        expand_add!(Ham, hamiltonian(subsys,t), expander[1])
    end
end

function liouvillian_dual_add!(liouv::Matrix{ComplexF64}, c::CompositeQSystem, t::Float64 )
    #Fast system superoperator calculator with liouvillian preallocated

    #Zero the preallocated operators
#    liouv[:] .= 0.0  # use either .= or fill!
    fill!(liouv, 0.0)

    #Update the subsystems with the parameteric interactions
    for pi in c.parametericInteractions
        update_params(c, pi, t)
    end

    #Add together subsystem Hamiltonians
    for (subsys, expander) in zip(c.subSystems, c.subSystemExpansions)
        expand_add!(liouv, transpose(hamiltonian(subsys, t)), expander[2], mult=-1im) # superoperator right
        expand_add!(liouv,           hamiltonian(subsys, t),  expander[3], mult= 1im) # superoperator left
    end

    #Add interactions
    for (subsys, expander) in zip(c.interactions, c.interactionExpansions)
        expand_add!(liouv, transpose(hamiltonian(subsys, t)), expander[2], mult=-1im) # superoperator right
        expand_add!(liouv,           hamiltonian(subsys, t),  expander[3], mult= 1im) # superoperator left
    end

    # Add the Liouvillian for the dissipators
    for (subsys, expander) in zip(c.dissipators, c.dissipatorExpansions)
        expand_add!(liouv, liouvillian_left(subsys,t),  expander[1]) # left
        expand_add!(liouv, liouvillian_right(subsys,t), expander[2]) # right
        expand_add!(liouv, liouvillian_bilat(subsys,t), expander[3]) # bilateral
    end
end

function liouvillian_add!(liouv::AbstractMatrix{T}, c::CompositeQSystem, t::Float64 ) where {T<:Number}
    #Fast system superoperator calculator with liouvillian preallocated

    #Zero the preallocated operators.
    if issparse(liouv)
        # TODO: check: is it safe to assume that we will not make liouv denser and denser?
        rows,cols,_ = findnz(liouv)
        for i = 1:length(rows)
            liouv[rows[i],cols[i]] = 0.0
        end
    else
        liouv[:] = 0.0
    end

    #Update the subsystems with the parameteric interactions
    for pi in c.parametericInteractions
        update_params(c, pi, t)
    end

    #Add together subsystem Hamiltonians
    for (subsys, expander) in zip(c.subSystems, c.subSystemExpansions)
        expand_add!(liouv, transpose(hamiltonian(subsys, t)), expander[2], mult= 1im) # superoperator right
        expand_add!(liouv,           hamiltonian(subsys, t),  expander[3], mult=-1im) # superoperator left
    end

    #Add interactions
    for (subsys, expander) in zip(c.interactions, c.interactionExpansions)
        expand_add!(liouv, transpose(hamiltonian(subsys, t)), expander[2], mult= 1im) # superoperator right
        expand_add!(liouv,           hamiltonian(subsys, t),  expander[3], mult=-1im) # superoperator left
    end

    # Add the Liouvillian for the dissipators
    for (subsys, expander) in zip(c.dissipators, c.dissipatorExpansions)
        expand_add!(liouv, liouvillian_left(subsys,t),  expander[1]) # left
        expand_add!(liouv, liouvillian_right(subsys,t), expander[2]) # right
        expand_add!(liouv, liouvillian_bilat(subsys,t)', expander[3]) # bilateral
    end
end

function expand(m::Matrix, actingOn::Vector, dims::Vector)
    #Expand an operator onto a larger Hilbert space
    # m: matrix form of  operator
    # actingOn: array of which subsystem index the operator should be acting on
    # dims: array of dimensions of all the subsystems

    @assert size(m, 1) == prod(dims[actingOn]) "Oops! Dimensions of matrix do not match dims argument."

    #Create the large matrix by tensoring on identity
    l = length(dims)
    eyeIndices = filter(x->!(x in actingOn), 1:l)
    M = isempty(eyeIndices) ? m : kron(m, eye(eltype(m), prod(dims[eyeIndices])))

    #Reshape into multi-dimensional array given by subsystem dimensions
    #Since we have a matrix we repeat for rows then columns
    M = reshape(M, tuple([dims; dims]...))

    #Permute magic
    forwardPerm = [actingOn; eyeIndices]
    reversePerm = invperm(forwardPerm)
    #Handle the way tensor product indices work (last subsystem is fastest)
    reversePerm = reverse((l+1) .- reversePerm)
    M = permutedims(M, [reversePerm; reversePerm .+ l])

    #Reshape back
    return reshape(M, prod(dims), prod(dims))
end

function expand(m::Matrix, indices::Vector, sizeM::Int )
    M = zeros(eltype(m), (sizeM, sizeM))
    for (ct, inds) in enumerate(indices)
        M[inds] = m[ct]
    end
    return M
end

function expand_add!(M::AbstractMatrix{T}, m::AbstractMatrix{U}, indices::Vector; mult=1.0 ) where {T<:Number,U<:Number}
    #Add to certain indices of M with terms from m according to expansion indices.
    for ct=1:length(indices)
        # M[indices[ct]] += m[ct]
        for idx = indices[ct]
            M[idx] += mult*m[ct]
        end
    end
end

function expand_indices(actingOn::Vector, dims::Vector)
    #Calculate the indices for expansion
    actingOnDim = prod(dims[actingOn])
    sm = (actingOnDim, actingOnDim)
    lenm = actingOnDim^2;
    M = expand(reshape([1:lenm;], sm), actingOn, dims)
    M_one_d = reshape(M, prod(size(M)))
    return IndexSet[findall(M_one_d .== x) for x in 1:lenm]
end


export InferenceAlgorithm, currentInferenceAlgorithm, messagePassingAlgorithm

"""
An `InferenceAlgorithm` specifies the computations for the quantities of interest.
"""
mutable struct InferenceAlgorithm
    id::Symbol
    posterior_factorization::PosteriorFactorization

    # Bookkeeping for faster lookup during assembly
    interface_to_schedule_entry::Dict{Interface, ScheduleEntry}
    target_to_marginal_entry::Dict{Union{Variable, Cluster}, MarginalEntry}

    # Fields for free energy algorithm assembly
    average_energies::Vector{Dict{Symbol, Any}}
    entropies::Vector{Dict{Symbol, Any}}
end

"""
Return currently active `InferenceAlgorithm`.
Create one if there is none.
"""
function currentInferenceAlgorithm()
    try
        return current_inference_algorithm
    catch
        return InferenceAlgorithm()
    end
end

function setCurrentInferenceAlgorithm(algo::InferenceAlgorithm)
     global current_inference_algorithm = algo
end

function InferenceAlgorithm(
    pfz=currentPosteriorFactorization();
    id=Symbol(""))

    setCurrentInferenceAlgorithm(
        InferenceAlgorithm(
            id,
            pfz,
            Dict{Interface, ScheduleEntry}(),
            Dict{Union{Variable, Cluster}, MarginalEntry}(),
            Dict{Symbol, Any}[],
            Dict{Symbol, Any}[]))
end

"""
Create a message passing algorithm to infer marginals over a posterior distribution
"""
function messagePassingAlgorithm(target_variables::Vector{Variable}=Variable[], # Quantities of interest
                                 pfz::PosteriorFactorization=currentPosteriorFactorization();
                                 ep_sites=Tuple[],
                                 id=Symbol(""),
                                 free_energy=false)

    if isempty(pfz.posterior_factors) # If no factorization is defined
        PosteriorFactor(pfz.graph, pfz=pfz, id=Symbol("")) # Contain the entire graph in a single posterior factor
    end

    # Set the EP sites for each corresponding posterior factor
    for ep_site in ep_sites
        site_node = pfz.graph.nodes[ep_site[1]] # Find node by id
        site_iface = site_node.i[ep_site[2]] # Find interface by id
        site_edge = site_iface.edge
        for (_, pf) in pfz.posterior_factors # Find corresponding posterior factor
            if site_edge in pf.internal_edges
                push!(pf.ep_sites, site_iface)
                break # Skip to next site
            end
        end
    end

    # Set the targets for each posterior factor
    for (_, pf) in pfz.posterior_factors
        setTargets!(pf, pfz, target_variables=Set(target_variables), free_energy=free_energy, external_targets=true)
    end

    # Infer schedule and marginal computations for each recogition factor
    for (_, pf) in pfz.posterior_factors
        schedule = messagePassingSchedule(pf)
        pf.schedule = condense(flatten(schedule)) # Inline all internal message passing and remove clamp node entries
        pf.marginal_table = marginalTable(pf)
    end

    # Populate fields for algorithm compilation
    algo = InferenceAlgorithm(pfz, id=id)
    assembleInferenceAlgorithm!(algo)
    free_energy && assembleFreeEnergy!(algo)

    return algo
end

messagePassingAlgorithm(target_variable::Variable,
                        pfz::PosteriorFactorization=currentPosteriorFactorization();
                        ep_sites=Tuple[],
                        id=Symbol(""),
                        free_energy=false) = messagePassingAlgorithm([target_variable], pfz; ep_sites=ep_sites, id=id, free_energy=free_energy)

function interfaceToScheduleEntry(algo::InferenceAlgorithm)
    mapping = Dict{Interface, ScheduleEntry}()
    for (id, pf) in algo.posterior_factorization
        pf_mapping = interfaceToScheduleEntry(pf.schedule)
        merge!(mapping, pf_mapping)
    end

    return mapping
end

function targetToMarginalEntry(algo::InferenceAlgorithm)
    mapping = Dict{Union{Cluster, Variable}, MarginalEntry}()
    for (id, pf) in algo.posterior_factorization
        pf_mapping = targetToMarginalEntry(pf.marginal_table)
        merge!(mapping, pf_mapping)
    end

    return mapping
end


## Copyright (c) 2015, Júlio Hoffimann Mendes <juliohm@stanford.edu>
##
## Permission to use, copy, modify, and/or distribute this software for any
## purpose with or without fee is hereby granted, provided that the above
## copyright notice and this permission notice appear in all copies.
##
## THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
## WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
## MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
## ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
## WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
## ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
## OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

function relaxation(distance::AbstractArray, auxdistances::AbstractArray, cutoff::Real)
  # patterns enabled in the training image
  enabled = !isinf(distance); npatterns = sum(enabled)

  # candidates with good overlap
  dbsize = all(distance[enabled] .== 0) ? npatterns : ceil(Int, cutoff*npatterns)
  overlapdb = selectperm(distance[:], 1:dbsize)

  # candidates in accordance with auxiliary data
  naux = length(auxdistances)
  softdb = fill(Int[], naux)

  patterndb = []
  softdistance = [copy(auxdistances[i]) for i=1:naux]
  frac = .1 * (dbsize / npatterns)
  while true
    softdbsize = ceil(Int, frac*npatterns)

    patterndb = overlapdb
    for n=1:length(softdistance)
      softdistance[n][softdb[n]] = Inf
      softdb[n] = [softdb[n]; selectperm(softdistance[n][:], 1:softdbsize-length(softdb[n]))]

      patterndb = quick_intersect(patterndb, softdb[n], length(distance))

      isempty(patterndb) && break
    end

    !isempty(patterndb) && break
    frac = min(frac + .1, 1)
  end

  patterndb
end

function quick_intersect(A::AbstractVector{Int}, B::AbstractVector{Int},
                         nbits::Integer)
  bitsA = falses(nbits)
  bitsB = falses(nbits)
  bitsA[A] = true
  bitsB[B] = true

  find(bitsA & bitsB)
end


# internal only
include("homotopies/toric_homotopy.jl")

# public, these should be linked on the top
include("homotopies/mixed_homotopy.jl")
include("homotopies/affine_chart_homotopy.jl")
include("homotopies/parameter_homotopy.jl")
include("homotopies/coefficient_homotopy.jl")
include("homotopies/extrinsic_subspace_homotopy.jl")
include("homotopies/intrinsic_subspace_homotopy.jl")
include("homotopies/straight_line_homotopy.jl")
include("homotopies/fixed_parameter_homotopy.jl")

"""
    fixed(H::Homotopy; compile::Union{Bool,Symbol} = $(COMPILE_DEFAULT[]))

Constructs either a [`CompiledHomotopy`](@ref) (if `compile = :all`), an
[`InterpretedHomotopy`](@ref) (if `compile = :none`) or a
[`MixedHomotopy`](@ref) (`compile = :mixed`).
"""
function fixed(H::Homotopy; compile::Union{Bool,Symbol} = COMPILE_DEFAULT[], kwargs...)
    if compile == true || compile == :all
        CompiledHomotopy(H; kwargs...)
    elseif compile == false || compile == :none
        InterpretedHomotopy(H; kwargs...)
    elseif compile == :mixed
        MixedHomotopy(H; kwargs...)
    else
        error("Unknown argument $compile for keyword `compile`.")
    end
end
fixed(H::AbstractHomotopy; kwargs...) = H

function set_solution!(x::AbstractVector, H::AbstractHomotopy, y::AbstractVector, t)
    x .= y
end
get_solution(H::AbstractHomotopy, x::AbstractVector, t) = copy(x)

start_parameters!(H::AbstractHomotopy, p) = H
target_parameters!(H::AbstractHomotopy, p) = H

ModelKit.taylor!(u, v::Val, H::AbstractHomotopy, tx, t, incremental::Bool) =
    taylor!(u, v, H, tx, t)


""" PIV PARAMETER OBJECT """
mutable struct PIVParameters
         corr::Union{ZNCC,FFT}          # cross-correlation algorithm, "zncc" or "fft"
    interArea::Tuple{Int64,Int64,Int64} # interrogation area size(s) in pixels
 searchMargin::Tuple{Int64,Int64,Int64} # search margin(s) in pixels
      overlap::Tuple{Int64,Int64,Int64} # pixel distance(s) between contiguous interrogation areas
        mpass::Int64    # multi-pass depth
        width::Int64    # exclusion radius around 1st peak for finding 2nd peak
         peak::String   # one of "", "gaussian", "centroid"
     sigNoise::String   # one of "", "peak2peak"
      filtFun::Function # Filtering function to skip cross-correlating certain interrogation areas (e.g. background)
    threshold::Float64  # skip cross correlation if filtFun( interrogation area ) < threshold
        mpFun::Function # scaling function for IA, SM and ovp during multipass
       mpReps::Array{Int64,1} # number of times each multipass iteration is repeated. Not implemented.
        units::Tuple{DU,DU,DU,TU} # spatial and temporal units of the analyzed data
         args::Tuple{III,III,III,I,I,S,S,Function,F,Function,Array{I,1}}
end

PP = PIVParameters;

# constructor
PIVParameters( corr::Union{ZNCC,FFT}, IA::UnI, SM::UnI, ovp::UnI, mpass::I, width::I, peak::S,
               sn::S, ffn::Function, th::F, mpfn::Function, mpReps::Array{I,1}, uns::UNTS
             ) = PIVParameters( corr, toIII(IA), toIII(SM), toIII(ovp), mpass, width, peak, sn,
                                ffn, th, mpfn, mpReps, uns, ( toIII(IA), toIII(SM), toIII(ovp),
                                mpass, width, peak, sn, ffn, th, mpfn, mpReps )
                              )

function parseCorr( corr::String )
    lowercase(corr) == "zncc" &&  return ZNCC();
    lowercase(corr) == "fft"  &&  return  FFT();
end

# interrogation area size, search margin and overlap can be specified for each dimension,
# or a sigle value can be applied to all dimensions.
toIII( input::Int64 ) = ( input, input, input )
toIII( input::Tuple{Int64,Int64} ) = ( input[1], input[2], 0 )
toIII( input::Tuple{Int64,Int64, Int64} ) = input

# constructor with default values
function setPIVParameters(; interArea::UnI=32, searchMargin::UnI=0, overlap::UnI=0,
                            mpass::I=1, width::I=2, corr::S="zncc",
                            peak::S="gaussian", sigNoise::S="",
                            filtFun=(x)->maxval(x), threshold::F=-1.0,
                            mpFun=(x,mp)->(x*mp), mpReps::Array{I,1}=[1,],
                            units=(1m,1m,1m,1second) )
    return PIVParameters( parseCorr(corr), interArea, searchMargin, overlap, mpass,
                          width, peak, sigNoise, filtFun, threshold, mpFun, mpReps, units )
end

function checkPIVParameters( dsize, N, params )
    !( N !== 2  ||  N !== 3 ) && error("Your input data isn't 2D nor 3D.")
    IA = params.interArea;
    if N == 2
         IA = params.interArea[1:2]
    end
    any( params.interArea .< 2 ) && error("interArea = 1 makes little sense.")
    any( IA .> dsize ) && error("interArea is bigger than the data")
    any( params.overlap .>= params.interArea ) && error("Overlap must be < IA size")

    if size(params.mpReps,1) !== params.mpass
        updateParameters!( params, :mpReps, ones( Int64, params.mpass ) )
    end
end

""" SYNTHETIC PARTICLES PARAMETER OBJECT """
mutable struct synthParameters
      n::Int64   # total number of particles, ignored when dens > 0
   dens::Int64   # particle density ( nºparticles/IA )
      d::Int64   # depth. If depth > 1, generates a volume
      w::Int64   # width of generated image/volume
      h::Int64   # height of generated image/volume
     i0::Float64 # max intensity of particles
     dt::Float64 # standard deviation of gaussian around each particles
     th::Float64 # standard deviation of gaussian around lightsheet
    err::Float64 # determines rendering radius around each pixel
      z::Int64   # light-sheet position. Default z = 0
  noise::Int64   # varible for controlling noise. Not really implemented.
    rad::Int64   # overwrites the rendering radius computed from err.
   mode::Bool    # switches between ( x, y, z ) or ( y, x, z ) coordinates
   args::Tuple{I,I,I,I,I,F,F,F,F,I,I,I,Bool}
end

SP = synthParameters;

# constructor
synthParameters( n::Int64, dens::Int64, d::Int64, w::Int64, h::Int64, i0::Float64, dt::Float64,
                 th::Float64, err::Float64, z::Int64, noise::Int64, rad::Int64, mode::Bool
               ) = synthParameters( n, dens, d, w, h, i0, dt, th, err, z, noise, rad, mode,
                                   (n, dens, d, w, h, i0, dt, th, err, z, noise, rad, mode)
                                  )

# constructor with default values
function setSynthParameters(; n=0::I, dens=0::I, d=1::I, w=300::I, h=300::I,
                              z=0::I, i0=255.0::F, dt=3.0::F, err=0.1::F,
                              th=10.0::F, noise=0::I, rad=0::I, mode="whd"::S )

    return synthParameters( n, dens, d, w, h, i0, dt, th, err, z, noise, rad, mode=="whd" )
end

""" TRANSFORMATION PARAMETERS """
mutable struct transformParameters{N,M}
      kind::Symbol            # currently ony :translation is supported
     means::NTuple{N,Float64} # mean value of transform in x, y [ and z ] if N = 3
      vars::NTuple{N,Float64} # variance component in covariance matrix
 cov_ratio::NTuple{M,Float64} # covariance components in covariance matrix. -1 < 1
      args::Tuple{Symbol,NTuple{N,Float64},NTuple{N,Float64},NTuple{M,Float64}}
end

# constructor
transformParameters( kind::Symbol, means::NTuple{N,Float64}, vars::NTuple{N,Float64},
                     covs::NTuple{M,Float64}
                   ) where {N,M} = transformParameters{N,M}( kind, means, vars, covs,
                                                            (kind, means, vars, covs)
                                                           )

TP2D = transformParameters{2,1}
TP3D = transformParameters{3,3}
TP   = Union{TP2D,TP3D}

# constructor with default values
function setTransformParameters(; kind=:translation, means=(1.0, 1.0), vars=(eps(Float64),
								  eps(Float64)), cov_ratio=(0.0,) )

    return transformParameters( kind, means, vars, cov_ratio );
end

to2DTransform( t2D::TP2D ) = t2D

function to2DTransform( t3D::TP3D )
    means     = (  t3D.means[1], t3D.means[2] );
    vars      = (  t3D.vars[1] ,  t3D.vars[2] );
    cov_ratio = (  t3D.cov_ratio[1], );

    return transformParameters{2,1}( t3D.kind, means, vars, cov_ratio );
end

function to3DTransform( t2D::TP2D )
    means     = (   t2D.means[1]  ,   t2D.means[2]  , (t2D.means[1] + t2D.means[2])/2 );
    vars      = (    t2D.vars[1]  ,    t2D.vars[2]  , ( t2D.vars[1] +  t2D.vars[2])/2 );
    cov_ratio = ( t2D.cov_ratio[1], t2D.cov_ratio[1], t2D.cov_ratio[1] );

    return transformParameters{3,3}( t2D.kind, means, vars, cov_ratio );
end

to3DTransform( t3D::TP3D ) = t3D

""" EVALUATION PARAMETER OBJECT """
mutable struct metaParameters
 metaloop::Int64    # determines the number of iterations where the changing variable goes from its "min" to "max" values
 variable::Symbol   # specifies what variable will be changed each iteration.
      min::Float64  # minimum value of changing variable
      max::Float64  # maximum value of changing variable
  repeats::Int64    # number of measurements for computing bias and error
     args::Tuple{Int64,Symbol,Float64,Float64,Int64}
end

MP = metaParameters;

metaParameters( metaloop::Int64, variable::Symbol, min::Float64, max::Float64, repeats::Int64
              ) = metaParameters( metaloop, variable, min, max, repeats,
                                 (metaloop, variable, min, max, repeats)
                                )

"""
    Creates a set of parameters for directing PIV evaluation. Accepted parameters are:
        variable (  Symbol ): a symbol indicating what variable should be evaluated. Can be any parameters
                              in synthParameters, PIVParameters or transformParameters.
        metaloop (  Int64  ): number of iterations
            min  ( Float64 ): min value of changing variable
            max  ( Float64 ): max value of changing variable
        repeats  (  Int64  ): number of repetitions
"""
function setMetaParameters( ; metaloop=0::I, var=Symbol("")::Symbol, min=0.0::F,  max=0.0::F, repeats=1::I )

    return metaParameters( metaloop, var, min,  max, repeats )
end

# function to update parameters, and also update the .args tuple, which was created to pass parameters
# by tuple deconstruction "parameter.args..."

function updateParameters!( params, field, newVal )
    hasfield = false;
    fieldidx =     0;
    for e in fieldnames( typeof(params) )
        fieldidx += 1;
        if ( e == field )
            hasfield = true;
            setproperty!( params, e, newVal )
            break
        end
    end

    ( !hasfield ) && return nothing;

    if typeof(params) == PIVParameters
        fieldidx -= 1 # ignore corr parameter
    end
    newargs = [ arg for arg in params.args ]
    newargs[ fieldidx ] = newVal;
    setproperty!( params, :args, Tuple(newargs) )
end


# Test fluent functionality
path = joinpath(dirname(pathof(PDDL)), "..", "test", "fluents")

domain = load_domain(joinpath(path, "domain.pddl"))
@test domain.name == Symbol("zeno-travel")
@test domain.functions[:distance] == pddl"(distance ?c1 ?c2)"
@test domain.functypes[:fuel] == [:aircraft]

# Test for static functions
@test pddl"(capacity ?a)" in get_static_functions(domain)
@test length(get_static_functions(domain)) == 5

problem = load_problem(joinpath(path, "problem.pddl"))
@test problem.metric == (-1, pddl"(+ (* 4 (total-time)) (* 5 (total-fuel-used)))")

state = init_state(problem)

# Person 1 boards plane 1
state = execute(pddl"(board person1 plane1 city0)", state, domain)
@test state[domain, pddl"(onboard plane1)"] == 1

# Plane 1 flies from city 0 to city 2
state = execute(pddl"(fly plane1 city0 city2)", state, domain)
@test state[domain, pddl"(total-fuel-used)"] == 3100

# Person 1 debarks at city 2
state = execute(pddl"(debark person1 plane1 city2)", state, domain)
@test state[domain, pddl"(at person1 city2)"] == true

# Plane 1 refuels at city 2
state = execute(pddl"(refuel plane1 city2)", state, domain)
@test state[domain, pddl"(fuel plane1)"] == 10232

# Person 2 boards plane 1 at city2
state = execute(pddl"(board person2 plane1 city2)", state, domain)
@test state[domain, pddl"(in person2 plane1)"] == true

# Plane 1 flies from city 2 to city 0
state = execute(pddl"(fly plane1 city2 city0)", state, domain)
@test state[domain, pddl"(total-fuel-used)"] == 6200

# Person 2 debarks at city 0
state = execute(pddl"(debark person2 plane1 city0)", state, domain)
@test state[domain, pddl"(at person2 city0)"] == true

# Plane 1 refuels at city 0
state = execute(pddl"(refuel plane1 city0)", state, domain)
@test state[domain, pddl"(fuel plane1)"] == 10232

# Plane 1 zooms from city 0 to city 1
state = execute(pddl"(zoom plane1 city0 city1)", state, domain)
@test state[domain, pddl"(total-fuel-used)"] == 16370

# The whole plan took 9 steps
@test state[domain, pddl"(total-time)"] == 9

# Check if goal is satisfied
@test satisfy(problem.goal, state, domain)[1] == true

# Test execution of entire plan
state = init_state(problem)
plan = @pddl(
    "(board person1 plane1 city0)",
    "(fly plane1 city0 city2)",
    "(debark person1 plane1 city2)",
    "(refuel plane1 city2)",
    "(board person2 plane1 city2)",
    "(fly plane1 city2 city0)",
    "(debark person2 plane1 city0)",
    "(refuel plane1 city0)",
    "(zoom plane1 city0 city1)"
)
state = execute(plan, state, domain)
@test satisfy(problem.goal, state, domain)[1] == true