# Test _all_function_variables
@testset "_all_function_variables" begin
    # initialize model and references
    m = InfiniteModel()
    @infinite_parameter(m, 0 <= par <= 1)
    @infinite_variable(m, inf(par))
    @point_variable(m, inf(0), pt)
    @hold_variable(m, hold)
    meas = MeasureRef(m, 1)
    m.meas_to_name[1] = "meas"
    # test for variable reference
    @testset "Variable" begin
        @test InfiniteOpt._all_function_variables(par) == [par]
        @test InfiniteOpt._all_function_variables(inf) == [inf]
        @test InfiniteOpt._all_function_variables(meas) == [meas]
    end
    # test for GenericAffExpr
    @testset "AffExpr" begin
        # make expressions
        aff1 = meas + 2par + hold
        aff2 = zero(GenericAffExpr{Float64, GeneralVariableRef})
        # test expressions
        @test InfiniteOpt._all_function_variables(aff1) == [meas, par, hold]
        @test InfiniteOpt._all_function_variables(aff2) == GeneralVariableRef[]
    end
    # test for GenericQuadExpr
    @testset "QuadExpr" begin
        # make expressions
        quad1 = pt^2 + inf * pt - meas + 2par + hold
        quad2 = pt^2 + inf * pt
        quad3 = zero(GenericQuadExpr{Float64, GeneralVariableRef})
        # test expressions
        @test InfiniteOpt._all_function_variables(quad1) == [meas, par, hold, pt, inf]
        @test InfiniteOpt._all_function_variables(quad2) == [pt, inf]
        @test InfiniteOpt._all_function_variables(quad3) == GeneralVariableRef[]
    end
    # test backup
    @testset "Fallback" begin
        @variable(Model(), x)
        @test_throws ErrorException InfiniteOpt._all_function_variables(x)
    end
end

# Test comparisons
@testset "Comparisons" begin
    # initialize model and references
    m = InfiniteModel()
    @infinite_parameter(m, 0 <= par <= 1)
    @infinite_parameter(m, 0 <= par2 <= 1)
    @infinite_variable(m, inf(par))
    @infinite_variable(m, inf2(par, par2))
    @point_variable(m, inf(0), pt)
    @hold_variable(m, hold)
    red = InfiniteOpt.ReducedInfiniteVariableRef(m, 42)
    # test AffExpr comparison
    @testset "Base.:(==) AffExpr" begin
        @test par + par2 + inf - 2 == par + (par2 + inf) - 2
        @test 0.25red - inf == 0.25red - inf
        @test red + 3 - inf != par + red
        @test red + 3 - inf != red + 3 - hold
    end
    # test QuadExpr comparison
    @testset "Base.:(==) QuadExpr" begin
        @test par * inf + par2 + inf - 2 == par * inf + (par2 + inf) - 2
        @test red * inf - inf == red * inf - inf
        @test red * inf + 3 - inf != par * inf2 + red
        @test par * par2 + inf * inf2 == par * par2 + inf * inf2
        @test par * par2 + inf * inf2 != par * par2
        @test par * par2 + 2 * inf * inf2 !=  par * par2 + inf * inf2
        @test par * inf + par2 + inf - 2 != par * inf + (par2 + inf) - 3
    end
end

# Test _all_parameter_refs
@testset "_all_parameter_refs" begin
    # initialize model and references
    m = InfiniteModel()
    @infinite_parameter(m, 0 <= par <= 1)
    @infinite_parameter(m, 0 <= par2 <= 1)
    @infinite_variable(m, inf(par))
    @infinite_variable(m, inf2(par, par2))
    @point_variable(m, inf(0), pt)
    @hold_variable(m, hold)
    red = InfiniteOpt.ReducedInfiniteVariableRef(m, 42)
    m.reduced_info[42] = ReducedInfiniteInfo(inf2, Dict(1 => 0.5))
    # test for finite variable reference
    @testset "FiniteVariable" begin
        @test InfiniteOpt._all_parameter_refs(pt) == ()
        @test InfiniteOpt._all_parameter_refs(hold) == ()
    end
    # test for infinite variable reference
    @testset "InfiniteVariable" begin
        @test InfiniteOpt._all_parameter_refs(inf) == (par, )
    end
    # test for parameter reference
    @testset "Parameter" begin
        @test InfiniteOpt._all_parameter_refs(par) == (par, )
    end
    # test for reduced infinite variable reference
    @testset "ReducedInfinite" begin
        @test InfiniteOpt._all_parameter_refs(red) == (par2, )
    end
    # test for GenericAffExpr
    @testset "AffExpr" begin
        # make expressions
        aff1 = inf + inf2 + pt - 3
        aff2 = pt + hold - 2
        # test expressions
        @test InfiniteOpt._all_parameter_refs(aff1) == (par, par2)
        @test InfiniteOpt._all_parameter_refs(aff2) == ()
    end
    # test for GenericQuadExpr
    @testset "QuadExpr" begin
        # make expressions
        quad1 = inf * inf2 + inf + inf2 + pt - 3 - par
        quad2 = pt * pt + pt + hold - 2
        # test expressions
        @test InfiniteOpt._all_parameter_refs(quad1) == (par, par2)
        @test InfiniteOpt._all_parameter_refs(quad2) == ()
    end
end

# Test _remove_variable
@testset "_remove_variable" begin
    # initialize model and references
    m = InfiniteModel()
    @infinite_parameter(m, 0 <= par <= 1)
    @infinite_variable(m, inf(par))
    @point_variable(m, inf(0), pt)
    @hold_variable(m, hold)
    meas = MeasureRef(m, 1)
    m.meas_to_name[1] = "meas"
    # test for GenericAffExpr
    @testset "AffExpr" begin
        # make expressions
        aff1 = meas + 2par + hold
        aff2 = zero(GenericAffExpr{Float64, GeneralVariableRef})
        # test expressions
        @test isa(InfiniteOpt._remove_variable(aff1, hold), Nothing)
        @test !haskey(aff1.terms, hold)
        @test isa(InfiniteOpt._remove_variable(aff2, inf), Nothing)
    end
    # test for GenericQuadExpr
    @testset "QuadExpr" begin
        # make expressions
        quad1 = pt^2 + inf * pt - meas + 2par + hold
        quad2 = zero(GenericQuadExpr{Float64, GeneralVariableRef})
        # test expressions
        quad = copy(quad1)
        @test isa(InfiniteOpt._remove_variable(quad, inf), Nothing)
        @test !haskey(quad.aff.terms, inf)
        @test !haskey(quad.terms, UnorderedPair{GeneralVariableRef}(inf, pt))
        quad = copy(quad1)
        @test isa(InfiniteOpt._remove_variable(quad, pt), Nothing)
        @test !haskey(quad.terms, UnorderedPair{GeneralVariableRef}(inf, pt))
        @test !haskey(quad.terms, UnorderedPair{GeneralVariableRef}(pt, pt))
        @test isa(InfiniteOpt._remove_variable(quad2, inf), Nothing)
    end
end

# Test destructive_add!
# @testset "JuMP.destructive_add!" begin
#     # initialize model and references
#     m = InfiniteModel()
#     @infinite_parameter(m, 0 <= par <= 1)
#     @infinite_parameter(m, 0 <= par2 <= 1)
#     @infinite_variable(m, inf(par))
#     @infinite_variable(m, inf2(par, par2))
#     @hold_variable(m, hold)
#     # test variable, constant, variable
#     @testset "Var, Constant, Var" begin
#         @test destructive_add!(inf, 2., inf2) == inf + 2inf2
#         @test destructive_add!(inf, 2., hold) == inf + 2hold
#     end
# end

# Test _set_variable_coefficient!
@testset "_set_variable_coefficient!" begin
    # initialize model and references
    m = InfiniteModel()
    @infinite_parameter(m, 0 <= par <= 1)
    @infinite_parameter(m, 0 <= par2 <= 1)
    @infinite_variable(m, x(par))
    @infinite_variable(m, y(par, par2))
    @hold_variable(m, z)
    # test with GeneralVariableRef
    @testset "GeneralVariableRef" begin
        @test InfiniteOpt._set_variable_coefficient!(x, x, 2) == 2 * x
        @test InfiniteOpt._set_variable_coefficient!(z, x, 2) == z + 2x
    end
    # test with GenericAffExpr
    @testset "AffExpr" begin
        @test InfiniteOpt._set_variable_coefficient!(x + z, x, 2) == 2x + z
        @test InfiniteOpt._set_variable_coefficient!(x + z, y, 2) == x + z + 2y
    end
    # test with GenericQuadExpr
    @testset "QuadExpr" begin
        @test InfiniteOpt._set_variable_coefficient!(y ^2 + x, x, 2) == y^2 + 2x
        @test InfiniteOpt._set_variable_coefficient!(y^2 + x, y, 2) == y^2 + x + 2y
    end
    # test fallabck
    @testset "Fallback" begin
        @test_throws ErrorException InfiniteOpt._set_variable_coefficient!(2, x, 2)
    end
end


include("delegate.jl")

include("Random.jl")
include("Lattice.jl")
include("Configuration.jl")
include("MeanFieldTheory.jl")
include("VMCMatrix.jl")
include("Move.jl")
include("Wavefunction.jl")
include("SwapWavefunction.jl")
include("SwapWavefunctionNS.jl")
include("Measurement.jl")
include("Walk.jl")
include("Simulation.jl")


import DomainSets: Interval, Ball, infimum, supremum

@deprecate IntervalDomain(a,b) Interval(a,b)
@deprecate CircleDomain() Ball()

# type piracy on Interval for downstream compatibility to be reverted once upgrade is complete
function Base.getproperty(domain::Interval, sym::Symbol)
    if sym === :lower
        @warn "domain.lower is deprecated, use infimum(domain) instead"
        return infimum(domain)
    elseif sym === :upper
        @warn "domain.upper is deprecated, use supremum(domain) instead"
        return supremum(domain)
    else
        return getfield(domain, sym)
    end
end


import .StaticArrays: StaticArray, StaticVector, MArray, SArray, SVector, MVector, MMatrix, SOneTo, SUnitRange, Size, index_size, index_sizes, StaticIndexing, _ind, similar_type
import .StaticArrays: LinearAlgebra

import Base: Matrix, Array

# TODO: Add methods for other matrix-creation functions, such as zeros() and rand().

SOneTo(::StaticOneTo{L}) where {L} = SOneTo{L}()
StaticOneTo(::SOneTo{L}) where {L} = StaticOneTo{L}()
staticlength(r::SOneTo) = StaticOneTo(r)
static(r::SOneTo) = StaticOneTo(r)

# TODO: Maybe just SOneTo from StaticArrays instead of defining StaticOneTo?

Base.@pure SUnitRange(r::LengthUnitRange) = SUnitRange{Int(first(r)), Int(length(r))}()
LengthUnitRange(::SUnitRange{Start, L}) where {Start, L} = LengthUnitRange(static(Start-1), static(L))
staticlength(r::SUnitRange) = LengthUnitRange(r)
static(r::SUnitRange) = LengthUnitRange(r)

Size(::LengthRange{T,Z,S,StaticInteger{L}}) where {T,Z,S,L} = Size(Int(L))
Size(s::Tuple{StaticInteger, Vararg{StaticInteger}}) = Size(Int.(s))
Size(s::StaticInteger, ss::StaticInteger...) = Size((s, ss...))
static(::Size{S}) where {S} = static.(S)

for AT in (Array, MArray, LinearAlgebra.Adjoint{<:Any, <:Array}, LinearAlgebra.Transpose{<:Any, <:Array}), RT in (LengthStepRange{<:Integer,Z,S,<:StaticInteger} where {Z,S}, LengthUnitRange{<:Integer,Z,<:StaticInteger} where {Z})
    Base.getindex(A::AT, r::RT) = MVector(ntuple(i -> A[r[i]], length(r)))
end

for AT in (SArray,), RT in (LengthStepRange{<:Integer,Z,S,<:StaticInteger} where {Z,S}, LengthUnitRange{<:Integer,Z,<:StaticInteger} where {Z})
    Base.getindex(A::AT, r::RT) = SVector(ntuple(i -> A[r[i]], length(r)))
end

# Allow "end" to become static when indexing into static arrays
@inline maybe_static(::typeof(lastindex), A::StaticArray) = static(lastindex(A))
@inline maybe_static(::typeof(lastindex), A::StaticArray, d::StaticInteger) = static(lastindex(A, i))

@inline maybe_static(::typeof(size), A::StaticArray) = map(static, size(A))
@inline maybe_static(::typeof(size), A::StaticArray, d::Integer) = static(size(A, d))

@inline (::Type{T})(g::Base.Generator{<:LengthRange}) where {T<:StaticVector} = T(Tuple(g))
@inline (::Type{T})(iter::LengthRange) where {T<:StaticVector} = T(Tuple(iter))

@inline function (::Type{T})(g::Base.Generator{<:Base.Iterators.ProductIterator{<:Tuple{StaticLengthRange, Vararg{StaticLengthRange}}}}) where {T<:StaticArray}
    sz = Size(size(g))
    data = Tuple(g)
    ST = similar_type(T, eltype(data), sz)
    ST <: T || error("Generator yields the wrong type of static array.")
    ST(data)
end

Base.@propagate_inbounds function Base.convert(::Type{SA}, a::SubArray{T,N,<:AbstractArray{T},<:Tuple{StaticLengthRange, Vararg{StaticLengthRange}}}) where {SA <: StaticArray, T, N}
    Z = similar_type(SA, T, Size(size(a)))
    Z <: SA || error("Input array yields the wrong type of static array.")
    Z(Tuple(a))
end

# TODO: Interface to StaticArrays for multi-dimensional indexing


# ------------------------------------------------------------------
# Licensed under the ISC License. See LICENSE in the project root.
# ------------------------------------------------------------------

"""
    RandomPath(domain)

A random path on a spatial `domain`.
"""
struct RandomPath{D<:AbstractDomain} <: AbstractPath{D}
  domain::D
  permut::Vector{Int}

  function RandomPath{D}(domain, permut) where {D<:AbstractDomain}
    @assert length(permut) == npoints(domain) "incorrect dimension"
    new(domain, permut)
  end
end

RandomPath(domain) = RandomPath{typeof(domain)}(domain, randperm(npoints(domain)))

Base.iterate(p::RandomPath, state=1) = state > npoints(p.domain) ? nothing : (p.permut[state], state + 1)

# ------------
# IO methods
# ------------
function Base.show(io::IO, path::RandomPath)
  print(io, "RandomPath")
end

function Base.show(io::IO, ::MIME"text/plain", path::RandomPath)
  println(io, path)
end


const plot_functions = Dict(
    :plot => [plot, (args...; kwargs...) -> plot!(shared.plt, args...; kwargs...)],
    :scatter => [scatter, (args...; kwargs...) -> scatter!(shared.plt, args...; kwargs...)],
    :groupedbar => [groupedbar, (args...; kwargs...) -> groupedbar!(shared.plt, args...; kwargs...)],
    :boxplot => [boxplot, (args...; kwargs...) -> boxplot!(shared.plt, args...; kwargs...)],
    :violin => [violin, (args...; kwargs...) -> violin!(shared.plt, args...; kwargs...)],
    :marginalhist => [marginalhist, marginalhist]
)

function get_plotvalues(df)
    xvalues = ComboBoxType("X AXIS", ComboBoxEntry.(string.(colnames(df))), true)
    ylist = string.(union([:hazard, :density, :cumulative],colnames(df)))
    yvalues = ComboBoxType("Y AXIS", ComboBoxEntry.(ylist), true)
    plotlist = [
        "plot",
        "scatter",
        "groupedbar",
        "boxplot",
        "violin",
        "marginalhist"
    ]
    plot_type = ComboBoxType("PLOT TYPE", ComboBoxEntry.(plotlist), false)
    axislist = ["pointbypoint", "continuous", "binned", "discrete"]
    axis_type = ComboBoxType("AXIS TYPE", ComboBoxEntry.(axislist) , false)
    errorlist = union(["none", "bootstrap", "across"], "across " .* string.(colnames(df)))
    compute_error = ComboBoxType("COMPUTE ERROR",  ComboBoxEntry.(errorlist), false)
    namelist = union(["pointbypoint"], "point=" .* string.(colnames(df)))
    dataperpoint = ComboBoxType("DATA PER POINT",  ComboBoxEntry.(namelist), false)
    return [xvalues, yvalues, plot_type, axis_type, compute_error, dataperpoint]
end

get_kwargs(s) = s == "" ? [] : [(x.args[1], eval(x.args[2])) for x in parse("("*s*",)").args]

get_func(s) = (s == "") ? mean : eval(parse(s))

function get_plot!(shared, in_place)
    df, selectlist, plotvalues = shared.df, shared.selectlist, shared.plotvalues
    selectdata = choose_data(shared)
    xval, yval, line, axis_type, compute_error, dataperpoint =
        getfield.(plotvalues, :chosen_value)
    extra_kwargs = get_kwargs(shared.plotkwargs.value)
    x_info, y_info = plotvalues[1].text_info, plotvalues[2].text_info
    xfunc = get_func(x_info)
    yfunc = get_func(y_info)
    isrecipe = !(Symbol(line) in [:plot, :scatter, :groupedbar])
    isgroupapply = !isrecipe && !(Symbol(axis_type) == :pointbypoint)
    xlabel, ylabel = xval, yval
    plot_func = (args...; kwargs...) -> plot_functions[Symbol(line)][in_place ? 2 : 1](args...;
                xlabel = xlabel, ylabel = ylabel, kwargs..., extra_kwargs...)
    group_cols = [Symbol(col.name) for col in selectlist if col.split]

    maybe_nbins = Symbol(axis_type) == :binned ? (round(Int64, 101-shared.smoother.value),) : ()

    s = GroupedErrors.ColumnSelector(selectdata)
    s = GroupedErrors._splitby(s, group_cols)
    if (Symbol(axis_type) == :pointbypoint) || isrecipe
        if !isrecipe && ('=' in dataperpoint)
            s = GroupedErrors._across(s, Symbol(split(dataperpoint, '=')[2]))
            if check_equality() && (':' in shared.splitting_var.chosen_value)
                splitting_var = Symbol(split(shared.splitting_var.chosen_value, ':')[2])
                s = GroupedErrors._compare(s, splitting_var)
            end
            s = GroupedErrors._x(s, Symbol(xval), xfunc)
            s = GroupedErrors._y(s, Symbol(yval), yfunc)
        else isrecipe
            s = GroupedErrors._x(s, Symbol(xval))
            s = GroupedErrors._y(s, Symbol(yval))
        end
    else
        compute_error = convert_error_type(compute_error)
        if compute_error[1] == :across
            s = GroupedErrors._across(s, compute_error[2])
        elseif compute_error[1] == :bootstrap
            s = GroupedErrors._bootstrap(s, compute_error[2])
        end
        s = GroupedErrors._x(s, Symbol(xval), Symbol(axis_type), maybe_nbins...)
        if Symbol(axis_type) == :continuous
            if Symbol(yval) in colnames(selectdata)
                s = GroupedErrors._y(s, :locreg, Symbol(yval), span = (shared.smoother.value+1.0)/100)
            elseif Symbol(yval) in [:density, :hazard]
                s = GroupedErrors._y(s, Symbol(yval), bandwidth = (shared.smoother.value+1.0)*std(column(selectdata, Symbol(xval)))/200)
            else
                s = GroupedErrors._y(s, Symbol(yval))
            end
        else
            s = GroupedErrors._x(s, Symbol(xval), xfunc)
            if Symbol(yval) in colnames(selectdata)
                s = GroupedErrors._y(s, :locreg, Symbol(yval), estimator = yfunc)
            else
                s = GroupedErrors._y(s, Symbol(yval))
            end
        end
    end
    shared.plt = Symbol(line) == :plot ? @plot(s, plot_func(), :ribbon) : @plot(s, plot_func())
    get(s.kw, :compare, false) && Plots.abline!(shared.plt, 1, 0, legend = false)
end

function convert_error_type(s::AbstractString)
    n = search(s, ' ')
    if n == 0
        if Symbol(s) == :bootstrap
            return (:bootstrap, 1000)
        elseif Symbol(s) == :across
            return (:across, :all)
        else
            return (Symbol(s),)
        end
    else
        return (Symbol(s[1:n-1]), Symbol(s[n+1:end]))
    end
end


# This is a general type for Adaptive selection based on Area-under-curve
# multi-armed bandit as described in the paper:
#   Francois-Michel De Rainville et al, "Sustainable Cooperative Coevolution
#   with a Multi-Armed Bandit", 2013
#

# An AdaptiveSelector selects between a set of integers that represent different
# operators, values etc. It does not have any info about what the things it selects
# between are, below we call them items. The set of items the selector selects
# from is typically static but might not be.
abstract type AdaptiveSelector end

# The default AdaptiveSelector is a random selector.
mutable struct RandomSelector <: AdaptiveSelector
  item_map::Dict{Int, Any}
  next_index::Int

  RandomSelector(items = []) = begin
    rs = new(Dict{Int, Any}(), 0)
    add_items(rs, items)
    rs
  end
end

# Returns an item map that maps from the indices of the current item set to
# each item.
function item_map(a::AdaptiveSelector)
  a.item_map # Override this if you do not save the item map in an instance var
end

# Get next free index. Returns unique index number each time it is called.
function get_next_free_index(a::AdaptiveSelector)
  a.next_index += 1
end

# Add an item. Returns its index.
function add_item(a::AdaptiveSelector, item)
  index = get_next_free_index(a)
  item_map(a)[index] = item
  index
end

function add_items(a::AdaptiveSelector, items)
  [add_item(a, item) for item in items]
end

# Remove an item given its index.
function remove_item_with_index(a::AdaptiveSelector, item_index::Int)
  delete!(a.item_map, item_index)
end

# Remove an item.
function remove_item(a::AdaptiveSelector, item)
  not_equals_item = (k,v) -> v != item
  filter!( not_equals_item, a.item_map )
end

# Select one item in the set of things that we select from. Returns both its
# index and itself / its value.
function select(a::AdaptiveSelector)
  ks = keys(a.item_map)
  randkey = ks[rand(1:length(ks))]
  a.item_map[randkey]
end

# Add reward for one item given its index. Default is to not do anything since
# the default selector is a random selector that need not care about rewards.
# A reward should be a positive value with larger values indicating more
# benefit when using the item.
function reward(a::AdaptiveSelector, index, reward)
  # Override for selectors that use rewards.
end

# We need a queue of fixed size to keep info about recent rewards.
mutable struct FixedSizeQueue
  queue::Array{Any,1}
  size::Int
  maxsize::Int
  index::Int

  FixedSizeQueue(maxsize) = begin
    q = Any[]
    [Base.push!(q, 0) for i in 1:maxsize]
    new(q, 0, maxsize, 0)
  end
end

size(fsq::FixedSizeQueue) = fsq.size
maxsize(fsq::FixedSizeQueue) = fsq.maxsize
is_full(fsq::FixedSizeQueue) = fsq.size == fsq.maxsize

function push!(fsq::FixedSizeQueue, item)
  fsq.queue[fsq.index + 1] = item
  fsq.index = mod(fsq.index + 1, fsq.maxsize)
  fsq.size = min(fsq.maxsize, fsq.size + 1)
end

function as_vector(fsq::FixedSizeQueue)
  v = Any[]
  for elem = fsq
    push!(v, elem)
  end
  v
end

##############
# Define FixedSizeQueue to be an iterator by defining start, done and next:
##############

Base.start(fsq::FixedSizeQueue) = (fsq.size, mod(fsq.index - fsq.size, fsq.maxsize))

Base.done(fsq::FixedSizeQueue, state) = state[1] == 0

function Base.next(fsq::FixedSizeQueue, state)
  elem = fsq.queue[state[2] + 1]
  return elem, (state[1]-1, mod(state[2]+1, fsq.maxsize))
end

mutable struct AucBanditSelector <: AdaptiveSelector
  window::FixedSizeQueue  # Window of latest (itemindex, reward) pairs
  ns::Int[]             # Number of times each item has been used within the current window.
  items::Any[]
  next_index::Int
  decay_factor::Float64
  exploration_factor::Float64

  AucBanditSelector(items = []; window_size = 50, decay_factor = 1.0,
    exploration_factor = 1.0) = begin

    aucb = new(FixedSizeQueue(window_size), Int[], Any[], 0,
      decay_factor, exploration_factor)

    add_items(aucb, items)

    aucb

  end
end

function add_item(a::AucBanditSelector, item)
  push!(a.ns, 0) # Add a zero to indicate how often it has been used => none!
  push!(a.items, item)
  length(a.items)
end

# Remove an item given its index.
function remove_item_with_index(a::AucBanditSelector, item_index::Int)
  splice!(a.items, item_index)
  splice!(a.ns, item_index)
end

# Remove an item.
function remove_item(a::AucBanditSelector, item)
  for i in 1:length(a.items)
    if a.items[i] == item
      remove_item_with_index(a, i)
      return i
    end
  end
end

function reward(a::AucBanditSelector, index, reward)
  # Update the ns counts by subtracting one from the one that is about the exit
  # the window.
  if is_full(a.window)
    (index, reward) = first(a.window)
    a.ns[index] -= 1
  end
  push!(a.window, (index, reward))
  a.ns[index] += 1
end

function select(a::AucBanditSelector)
  unused = a.ns == 0
  if any(unusued)
    # Randomly select one of the unused
    indices_to_unused = collect(1:length(a.ns))[unused]
    indices_to_unused[rand(1:length(indices_to_unused))]
  else
    # Select via the multi-armed bandit formula
    qs = calc_quality_values(a)
    vs = qs + a.exploration_factor * sqrt( 2 * log(sum(a.ns)) * (1 / a.ns) )
    items[argmin(vs)]
  end
end

function calc_quality_values(a:AucBanditSelector)
  qs = zeros(length(a.ns))
  for i in 1:length(a.ns)
    qs[i] = area_under_curve_credit_assignment(a, i)
  end
  qs
end

function sort_window(a::AucBanditSelector)
  sort(as_vector(a.window), by = (index, reward) -> reward, rev = true)
end

# We should extend this to calculate all credits in one go instead of sorting
# and looping for each index...
function area_under_curve_credit_assignment(a::AucBanditSelector, index)
  sorted_window = sort_window(a)
  wlength = length(sorted_window)
  y = q = 0.0
  rank = 1
  for (j, reward) in sorted_window
    # The AUC bandit skips the actual reward values and relies only on rank...
    reward = a.decay_factor^(rank-1) * (wlength - (rank - 1))
    if index == j
      y += reward
    else
      q += (y*reward)
    end
    rank += 1
  end
  q
end


macro disjunction(args...)
    pos_args, kw_args, _ = Containers._extract_kw_args(args)

    #get kw_args
    reformulation = filter(i -> i.args[1] == :reformulation, kw_args)
    if !isempty(reformulation)
        reformulation = reformulation[1].args[2]
    else
        throw(UndefKeywordError(:reformulation))
    end
    M = filter(i -> i.args[1] == :M, kw_args)
    M = !isempty(M) ? esc(M[1].args[2]) : :(missing)
    eps = filter(i -> i.args[1] == :eps, kw_args)
    eps = !isempty(eps) ? esc(eps[1].args[2]) : :(1e-6)
    name = filter(i -> i.args[1] == :name, kw_args)
    name = !isempty(name) ? esc(name[1].args[2]) : :(missing)

    #get args
    m = esc(pos_args[1])
    disj = [esc(a) for a in pos_args[2:end]]

    #build disjunction
    :(add_disjunction($m, $(disj...), reformulation = $reformulation, M = $M, eps = $eps, name = $name))
end


function get_erf_integral(JUPL_PATH)
    data = readdlm("$JUPL_PATH/assets/erf_integral.dat")
    erf_table = data[:,2]
end

function write_sim_parameters()
    filename = "output/parameters.csv"
    writedlm(filename, [@sprintf("%.5e", dtm) @sprintf("%d",nz) @sprintf("%.5e", dt_print) @sprintf("%.5e", tm_quiet) @sprintf("%.5e", L)], ",")
end

function open_files()
    	c1::Int = 0
    	c2::Int = 0
    	c3::Int = 0

    	for i = 1:(nspec+1)*3+2
    		if i==1
    			filename = "r.csv"
    		elseif i==(nspec+1)*3+2
    			filename = "efield.csv"
    		elseif (i>1) & (i<=(nspec+1)+1)
    			c1 += 1
    			filename = "vel" * string(c1) * ".csv"
    		elseif (i>(nspec+1)+1) & (i<=2*(nspec+1)+1)
                c2 += 1
    			filename = "den" * string(c2) * ".csv"
    		elseif (i>2*(nspec+1)+1) & (i<=3*(nspec+1)+1)
    			c3 += 1
    			filename = "temp" * string(c3) * ".csv"
    		end
            f = open("output/" * filename, "a")
    		@eval $(Symbol("f_$i")) = $f
    	end
end

function write_data(hydro)
	c1::Int = 0
	c2::Int = 0
	c3::Int = 0
    var = Array{Float64}(nz)

	for i = 1:(nspec+1)*3+2
		if i==1
			var = r
            filename = "r.csv"
		elseif i==(nspec+1)*3+2
			var = hydro.Efield
            filename = "efield.csv"
		elseif (i>1) & (i<=(nspec+1)+1)
			c1 += 1
			var = hydro.u[:,c1]
            filename = "vel" * string(c1) * ".csv"
		elseif (i>(nspec+1)+1) & (i<=2*(nspec+1)+1)
			c2 += 1
			if c2<=nspec
				var = 1.e-6 * hydro.rho[:,c2]	/ mi[c2]
			else #electrons
				var = 1.e-6 * hydro.rho[:,c2]	/ me
			end
            filename = "den" * string(c2) * ".csv"
		elseif (i>2*(nspec+1)+1) & (i<=3*(nspec+1)+1)
			c3 += 1
			var = hydro.T[:,c3] / qe_C
            filename = "temp" * string(c3) * ".csv"
		end
        f = @eval $(Symbol("f_$i"))
		for k = 1:nz
			writedlm(f, [@sprintf("%.24e", var[k])])
		end
	end
end

function close_files()
    for i = 1:(nspec+1)*3+2
        f = @eval $(Symbol("f_$i"))
        close(f)
    end
end

#---------------------------------------------------------
function write_all_data(hydro)
	c1::Int = 0
	c2::Int = 0
	c3::Int = 0
    var = Array{Float64}(nz)

    filename = "./output/U1D.csv"
    var = hydro.U1D
    writedlm(filename, var, ",")

    filename = "./output/U1D_p.csv"
    var = hydro.U1D_p
    writedlm(filename, var, ",")

    filename = "./output/U1D_c.csv"
    var = hydro.U1D_c
    writedlm(filename, var, ",")

    filename = "./output/F1D.csv"
    var = hydro.F1D
    writedlm(filename, var, ",")

    filename = "./output/G1D.csv"
    var = hydro.G1D
    writedlm(filename, var, ",")

    filename = "./output/R1D.csv"
    var = hydro.R1D
    writedlm(filename, var, ",")

    filename = "./output/C1D.csv"
    var = hydro.C1D
    writedlm(filename, var, ",")

    filename = "./output/T.csv"
    var = hydro.T
    writedlm(filename, var, ",")

end


# by lazarusA # HIDE
# using GLMakie # HIDE
using CairoMakie
CairoMakie.activate!() # HIDE
let
    x = y = -10:0.11:10
    y1d =  sin.(x) ./ x
    # 3D heatmap
    sinc2d(x,y) = sin.(sqrt.(x.^2 + y.^2))./sqrt.(x.^2+y.^2)
    fxy = [sinc2d(x,y) for x in x, y in y]

    fig = Figure(resolution = (700, 450))
    ax1 = Axis(fig, xlabel = "x", ylabel = "f(x)", xgridvisible = true,
            ygridvisible = true)
    ax2 = Axis(fig, bbox = BBox(140, 260, 260, 350), xticklabelsize = 12,
          yticklabelsize = 12, showgrid=false,
          title = "inset  at (140, 260, 260, 350)")
    line1 = lines!(ax1, x, y1d, color = :red)
    # inset
    hmap = heatmap!(ax2, x, y,fxy, colormap = :Spectral_11)
    limits!(ax2, -10,10,-10,10)
    hidespines!(ax2)
    ax2.yticks = [-10,0,10]
    ax2.xticks = [-10,0,10]

    leg = Legend(fig, [line1], ["sinc(x)"], halign = :right, valign = :top,
        tellheight = false, tellwidth = false,)
    cbar = Colorbar(fig, hmap, label ="sinc(x,y)", labelpadding = 5,
        tellheight = false, tellwidth = false, ticklabelsize = 12,
        width = 10, height = Relative(1.5/4), halign = :right, valign = :center)

    fig[1, 1] = ax1
    fig[1, 1] = leg
    fig[1, 1] = cbar
    fig
    #save("LineWithInsetHeatmap.png", fig, px_per_unit = 2)
    save(joinpath(@__DIR__, "output", "LineWithInsetHeatmap.png"), fig, px_per_unit = 2) # HIDE
end
using Pkg # HIDE
Pkg.status(["CairoMakie"]) # HIDE


module MetaImageFormat

using ImageCore
using ImageCore.FixedPointNumbers
using ImageCore.Colors
using ImageCore.MappedArrays
using ColorVectorSpace
# Other packages
using AxisArrays
using AxisArrays: HasAxes
using ImageAxes
using FileIO
# import Libz   # what compression does MetaImageFormat use? seems undocumented
using LinearAlgebra

string2type = Dict(
    "MET_UCHAR" => UInt8,
    "MET_SHORT" => Int16,
    "MET_USHORT" => UInt16,
    "MET_INT" => Int32,
    "MET_UINT" => UInt32,
    "MET_FLOAT" => Float32,
    "MET_UCHAR_ARRAY" => RGB{N0f8},
    "MET_USHORT_ARRAY" => RGB{N0f16},
)

# The opposite of string2type
type2string(::Type{UInt8})  = "MET_UCHAR"
type2string(::Type{Int16})  = "MET_SHORT"
type2string(::Type{UInt16}) = "MET_USHORT"
type2string(::Type{Int32})  = "MET_INT"
type2string(::Type{UInt32}) = "MET_UINT"
type2string(::Type{Float32}) = "MET_FLOAT"
type2string(::Type{RGB{T}}) where T = type2string(T)*"_ARRAY"

function space2axes(str)
    syms = Symbol.(tuple(str...))
    if length(syms) != 3 || !allunique(syms)
        @warn "AnatomicalOrientation string unrecognized, got $str. Using xyz instead."
        (:x, :y, :z)
    else
        syms
    end
end

axes2space(syms::NTuple{3,Symbol}) = join(syms)

# Parsing
VTuple{T} = Tuple{Vararg{T}}          # space-delimited tuple: 80 150

# This should list anything that DOESN'T parse to a string
# We replace True/False with the corresponding Bool
const parse_type = Dict(
    "NDims"=>Int,
    "ID"=>Int,
    "ParentID"=>Int,
    "BinaryData"=>Bool,
    "CompressedData"=>Bool,
    "BinaryDataByteOrderMSB"=>Bool,
    "ElementByteOrderMSB"=>Bool,
    "Color"=>RGBA{Float32},
    "Origin"=>VTuple{Float32},
    "Position"=>VTuple{Float32},
    "Offset"=>VTuple{Float32},
    "Orientation"=>Matrix{Float32},
    "Rotation"=>Matrix{Float32},
    "TransformMatrix"=>Matrix{Float32},
    "ElementSpacing"=>VTuple{Float32},
    "DimSize"=>VTuple{Int},
    "HeaderSize"=>Int,
    "SequenceID"=>VTuple{Int},
    "ElementMin"=>Float32,
    "ElementMax"=>Float32,
    "ElementNumberOfChannels"=>Int,
    "ElementSize"=>VTuple{Float32},
)

# const fieldorder = ["ObjectType", "NDims", "BinaryData",
#                     "BinaryDataByteOrderMSB", "ElementByteOrderMSB",
#                     "CompressedData",
#                     "TransformMatrix", "Offset", "CenterOfRotation",
#                     "sizes", "spacings", "space directions", "kinds",
#                     "centers", "centerings", "thickness",
#                     "axis mins", "axismins", "axis maxs", "axismaxs",
#                     "labels", "units",
#                     "min", "max", "old min", "oldmin", "old max", "oldmax",
#                     "block size", "blocksize", "endian", "encoding",
#                     "space units", "space origin", "measurement frame",
#                     "line skip", "lineskip", "byte skip", "byteskip",
#                     "sample units", "sampleunits",
#                     "data file", "datafile"]

const mymsb = ENDIAN_BOM == 0x01020304

# Don't extend FileIO.load
# Set mode to "r+" if you want to be able to modify values in the
# image and have them update in the disk file
function load(f::File{format"MetaImage"}, args...; mode="r", mmap=:auto)
    open(f, mode) do io
        load(io, args...; mode=mode, mmap=mmap)
    end
end

function load(io::Stream{format"MetaImage"}, Tuser::Type=Any; mode="r", mmap=:auto)
    # Assemble all the information about the array we're about to
    # read: element type, size, and the "meaning" of axes
    header = parse_header(io)
    Traw, need_bswap = raw_eltype(header)
    szraw = (header["DimSize"]...,)  # "sizes" may change in outer_eltype!, grab it now
    T, nd, perm = outer_eltype!(header, Traw, Tuser)
    axs = get_axes(header, nd)
    sz = get_size(axs)

    # Read the data
    iodata = find_datafile(io, header; mode=mode)
    compressed = get(header, "CompressedData", false)
    if compressed
        error("The MetaImage format doesn't appear to specifiy the compression algorithm, so not supported")
        # iodata = Libz.ZlibInflateInputStream(iodata)
    end

    can_mmap = !compressed

    if mmap == true && (!can_mmap)
        error("Cannot use memory-mapped for reading a compressed file")
    end

    # Use memory-mapping for large files
    do_mmap = can_mmap && (prod(szraw) > 10^8) && (mmap == :auto)
    do_mmap |= can_mmap && (mmap == true)

    if !compressed
        szraw = checked_size(Traw, szraw, sz, iodata)
    end

    if do_mmap
        A = Mmap.mmap(iodata, Array{Traw,length(szraw)}, szraw, position(iodata);
                      grow=false)
        if need_bswap
            f = mode == "r+" ? (bswap, bswap) : bswap
            A = mappedarray(f, A)
        end
    else
        A = Array{Traw}(undef, szraw...)
        read!(iodata, A)

        if need_bswap
            A = [bswap(a) for a in A]
        end
    end

    if perm == ()
        if T != eltype(A)
            A = need_bswap ? A = mappedarray(x->T(x), A) : reshape(reinterpret(T, vec(A)), sz)
        end
    else
        A = PermutedDimsArray(A, perm)
        if T<:Color
            A = colorview(T, A)
        end
    end

    isa(axs, Dims) ? A : AxisArray(A, axs)
end

# function save(f::File{format"MetaImage"}, img::AbstractArray; props::Dict = Dict{String,Any}(), keyvals=nothing, comments=nothing)
#     open(f, "w") do io
#         write(io, magic(format"NRRD"))
#         save(io, img; props=props, keyvals=keyvals, comments=comments)
#     end
# end

# function save{T}(io::Stream{format"NRRD"}, img::AbstractArray{T}; props::Dict = Dict{String,Any}(), keyvals=nothing, comments=nothing)
#     axs = axisinfo(img)
#     header = headerinfo(T, axs)
#     header_eltype!(header, T)
#     # copy fields from props to override those in header
#     for (k, v) in props
#         header[k] = v
#     end
#     v = version(header, !(keyvals==nothing || isempty(keyvals)))
#     write_header(io, v, header, keyvals, comments)
#     datafilename = get(props, "datafile", "")
#     if isempty(datafilename)
#         datafilename = get(props, "data file", "")
#     end
#     if isempty(datafilename)
#         nrrd_write(io, img)
#     else
#         println(io, "data file: ", datafilename)
#         if !get(props, "headeronly", false)
#             open(datafilename, "w") do file
#                 nrrd_write(file, img)
#             end
#         end
#     end
# end

# axisinfo(img) = axisinfo(HasAxes(img), img)
# axisinfo(::HasAxes{true}, img) = axes(img)
# axisinfo(::HasAxes, img) = size(img)

### Interpreting header settings

"""
    arraytype!(header) -> T, axs, perm, need_bswap

Analyze the `header` dictionary to extract the element-type `T`, size
or axes information `axs`, the permutation `perm` (if any) that julia
should use for "wrapping" the read data, and a boolean `need_bswap`
indicating whether the data need to be byte-swapped (to account for
differences in endianness). `T` includes any color information (in
which case a dimension of the array will be "consumed"). `axs` will be
a Dims-tuple in simple cases, or an `Axes` tuple (from AxisArrays.jl)
if dimensions are labeled or have their spatial information
(pixelspacing, spacedirections, etc) specified. `perm` is the
permutation needed to move the color data to the first dimension, or
an empty tuple if no permutation is required.

This function may modify the `header` dictionary (the reason for the !
in the name), so make a copy first if necessary.
"""
function arraytype!(header)
    Traw, need_bswap = raw_eltype(header)
    T, nd, perm = outer_eltype!(header, Traw)
    axs = get_axes(header, nd)
    T, axs, perm, need_bswap
end

"""
    arraytype(filename)

Parse MetaImage header and call `arraytype!(header)`. See
`arraytype!` for information about the return values.
"""
function arraytype(filename)
    header = parse_header(filename)
    arraytype!(header)
end

# function headerinfo(T, axs)
#     header = Dict{String,Any}()
#     Traw = raw_eltype(T)
#     header["type"] = type2string(Traw)
#     header["endian"] = myendian()
#     header["encoding"] = "raw"
#     if T <: Gray
#         val = gray(one(T))
#         val = isa(val, FixedPoint) ? reinterpret(val) : val
#         header["sample units"] = string("gray ", val)
#     elseif T <: Union{RGB,RGBA}
#         val = red(one(T))
#         val = isa(val, FixedPoint) ? reinterpret(val) : val
#         valfmt = isa(val, Integer) ? 'd' : 'f'
#         if T <: RGB
#             colstr = "rgb"
#             valfmtstr = "(%$valfmt,%$valfmt,%$valfmt)"
#             vals = (val,val,val)
#         else
#             colstr = "rgba"
#             valfmtstr = "(%$valfmt,%$valfmt,%$valfmt,%$valfmt)"
#             vals = (val,val,val,val)
#         end
#         fmtstr = "%s $valfmtstr"
#         x = (colstr, vals...)
#         header["sample units"] = @eval @sprintf($fmtstr, $(x...))
#     end
#     # Do the axes information
#     header["dimension"] = length(axs)
#     if isa(axs, Base.Indices)
#         axs = map(length, axs)
#     end
#     if isa(axs, Dims)
#         header["sizes"] = [axs...]
#     else
#         # axs is an Axis-tuple
#         header["sizes"] = [map(length, axs)...]
#         axnames = map(ax->axisnames(ax)[1], axs)
#         isspace = map(s->!startswith(string(s), "time"), axnames)
#         if haskey(axes2space, axnames)
#             header["space"] = axes2space[axnames]
#         else
#             header["space dimension"] = sum(isspace)
#         end
#         header["kinds"] = [isspc ? "domain" : "time" for isspc in isspace]
#         if !all(isdefaultname, axnames)
#             header["labels"] = [string(s) for s in axnames]
#         end
#         rng = map(ax->axisvalues(ax)[1], axs)
#         stepval = map(step, rng)
#         unitstr = map(x->isa(x, Quantity) ? string(unit(x)) : "", stepval)
#         spacing = map(x->isa(x, Quantity) ? ustrip(x) : x,        stepval)
#         if !all(x->x=="", unitstr)
#             header["units"] = [unitstr...]
#         end
#         if !all(x->x==1, spacing)
#             header["spacings"] = [spacing...]
#         end
#         origin = map(x->isa(x, Quantity) ? ustrip(x) : x, map(first, rng))
#         if any(x->x!=0, origin)
#             header["space origin"] = [origin[[isspace...]]...]
#         end
#     end
#     # Adjust the axes for color
#     if T <: Colorant && !(T <: AbstractGray)
#         header["dimension"] = length(axs)+1
#         unshift!(header["sizes"], length(T))
#         if haskey(header, "spacings")
#             unshift!(header["spacings"], NaN)
#         end
#         if haskey(header, "labels")
#             unshift!(header["labels"], lowercase(string(T.name.name)))
#         end
#         if haskey(header, "units")
#             unshift!(header["units"], "")
#         end
#         if !haskey(header, "kinds")
#             header["kinds"] = ["domain" for d = 1:length(axs)]
#         end
#         if T <: RGB
#             colkind = "RGB-color"
#         elseif T <: HSV
#             colkind = "HSV-color"
#         elseif T <: XYZ
#             colkind = "XYZ-color"
#         elseif T <: RGBA
#             colkind = "RGBA-color"
#         else
#             colkind = string(length(T), "-color")
#         end
#         unshift!(header["kinds"], colkind)
#     end
#     header
# end

# function version(header, has_keyvalue::Bool=false)
#     for n = length(version_reqs):-1:1
#         vr = version_reqs[n]
#         for f in vr
#             if haskey(header, f)
#                 return n
#             end
#         end
#     end
#     has_keyvalue ? 2 : 1
# end

"""
    raw_eltype(header) -> Traw, need_bswap
    raw_eltype(::Type{T}) -> Traw

Get the "basic" element type of the data, e.g., `UInt16` or
`Float32`.

This function does not try to determine whether the image is color
(`Traw` does not contain any color information), nor does it try to
interpret `Traw` as a `Normed` type.

See also: outer_eltype!, fixedtype.
"""
function raw_eltype(header)
    Traw = string2type[header["ElementType"]]
    msb = haskey(header, "BinaryDataByteOrderMSB") ? header["BinaryDataByteOrderMSB"] :
          haskey(header, "ElementByteOrderMSB") ? header["ElementByteOrderMSB"] : false
    need_bswap = msb != mymsb && sizeof(Traw) > 1
    Traw, need_bswap
end

#raw_eltype{C<:Colorant}(::Type{C}) = raw_eltype(eltype(C))
raw_eltype(::Type{C}) where C <: Colorant = raw_eltype(eltype(C))
raw_eltype(::Type{T}) where T <:FixedPoint = FixedPointNumbers.rawtype(T)
raw_eltype(::Type{T}) where T = T

"""
    fixedtype(Traw, header) -> Tu

Attempt to interpret type `Traw` in terms of FixedPoint numbers.
If `Traw` cannot be interpreted as `Normed`, `Tu = Traw`.
"""
function fixedtype(::Type{Traw}, header) where Traw <: Unsigned
    Traw == UInt8 && return N0f8
    if Traw == UInt16
        maxval = UInt16(get(header, "ElementMax", typemax(UInt16)))
        maxval == 0xffff && return N0f16
        maxval == 0x3fff && return N2f14
        maxval == 0x3fff && return N2f14
        maxval == 0x0fff && return N4f12
        maxval == 0x03ff && return N6f10
        maxval == 0x00ff && return N8f8
        error("unknown maxval $maxval")
    end
    Traw
end
fixedtype(::Type{Traw}, header) where Traw = Traw

function fixedtype_max(::Type{Traw}, mx) where Traw <: Unsigned
    fmx = log2(mx+1)
    if round(fmx) == fmx
        return Normed{Traw,round(Int,fmx)}
    end
    Traw
end

# header_eltype!(header, ::Type) = header
# function header_eltype!{T<:FixedPoint}(header, ::Type{T})
#     header["sample units"] = string("gray ", reinterpret(one(T)))
#     header
# end
# function header_eltype!{C<:Colorant}(header, ::Type{C})
#     _header_eltype!(header, C, eltype(C))
#     header
# end
# function _header_eltype!{C<:AbstractGray,T<:FixedPoint}(header, ::Type{C}, ::Type{T})
#     header["sample units"] = string("gray ", reinterpret(one(T)))
# end
# function _header_eltype!{C<:AbstractGray,T}(header, ::Type{C}, ::Type{T})
#     header["sample units"] = string("gray ", one(T))
# end
# function _header_eltype!{C<:AbstractRGB,T<:FixedPoint}(header, ::Type{C}, ::Type{T})
#     o = reinterpret(one(T))
#     header["sample units"] = "rgb ($o,$o,$o)"
# end
# function _header_eltype!{C<:AbstractRGB,T}(header, ::Type{C}, ::Type{T})
#     o = one(T)
#     header["sample units"] = "rgb ($o,$o,$o)"
# end
# function _header_eltype!{C<:XYZ}(header, ::Type{C}, ::Type)
#     header["sample units"] = "xyz (95.047,100.000,108.883)"
# end
# function _header_eltype!{C<:HSV}(header, ::Type{C}, ::Type)
#     header["sample units"] = "hsv (360, 0, 1)"
# end

"""
    colorant_eltype(C, T) -> Tc

Return a valid "inner" element type `Tc` for colorant type `C`. When
`T` != `Tc`, values must be "converted" before they can be interpreted
as type `C`.
"""
colorant_eltype(::Type{C}, ::Type{T}) where {C <: Colorant, T <: AbstractFloat} = C{T}
colorant_eltype(::Type{C}, ::Type{T}) where {C <: Colorant, T} = C{Float32}

"""
    UnknownColor{T,N}

An unknown Color. This type gets returned when one of the "kind"
settings is "3-color" or "4-color".
"""
struct UnknownColor{T,N} <: Color{T,N}
    col::NTuple{N,T}
end

"""
    outer_eltype!(header, Traw) -> T, nd, perm

Extract the julia array `eltype` `T`, the number of dimensions `nd`
**excluding** color/complex/vector/matrix element data, and any
permutation needed to put the eltype dimension first. Any dimensions
in the header corresponding to color (or if "kind" is set to one of
the vector types) will be "consumed" upon exit. `Traw` is the
element type as determined by `raw_eltype`.

See also: raw_eltype.
"""
function outer_eltype!(header, Traw, Tuser=Any)
    nd = header["NDims"]
    sz = header["DimSize"]
    length(sz) == nd || error("parsing of sizes: $sz is inconsistent with $nd dimensions")
    perm = ()
    T = fixedtype(Traw, header)
    T, nd, perm
end

function get_axes(header, nd)
    if haskey(header, "AnatomicalOrientation")
        axnames = space2axes(header["AnatomicalOrientation"])
    else
        axnames = nd == 2 ? (:x, :y) :
                  nd == 3 ? (:x, :y, :z) : error("$nd dimension not handled")
    end
    tform = haskey(header, "TransformMatrix") ? header["TransformMatrix"] :
            haskey(header, "Rotation") ? header["Rotation"] : Matrix(I, nd, nd)
    if tform != Matrix(I, nd, nd)
        error("rotations aren't yet handled, got $tform")
    end
    offset = get(header, "Offset", zeros(nd))
    spacing = get(header, "ElementSpacing", ones(nd))
    sz = header["DimSize"]
    rng = map((o, s, l) -> range(o, step=s, length=l), offset, spacing, sz)
    (map((name, r) -> Axis{name}(r), axnames, rng)...,)
end

get_size(sz::Dims) = sz
get_size(axs) = map(length, axs)

### Parsing

"""
    header = parse_header(io)

Parse the MetaImage header (an .mhd file). `io` should be positioned
at the beginning of the header file.

See also: write_header.
"""
function parse_header(io)
    header = Dict{String, Any}()
    # Read until we encounter a blank line, which is the separator
    # between the header and data
    line = strip(readline(io))
    while !isempty(line)
        idx = findfirst(isequal('='), line)
        idx == 0 && error("no colon found in $line")
        key, value = strip(line[1:idx-1]), strip(line[idx+1:end])
        T = get(parse_type, key, String)
        header[key] = meta_parse(T, value)
        line = strip(readline(io))
    end
    header
end

parse_header(s::Stream{format"MetaImage"}) = parse_header(stream(s))

function parse_header(filename::AbstractString)
    f = File{format"MetaImage"}(filename)
    open(f) do io
        parse_header(io)
    end
end

# """
#     write_header(io, version, header, [keyvals, [comments]])

# Write an NRRD header, as the top of the .nrrd or the separate .nhdr
# file. `io` should be positioned just after the initial "NRRD" in the
# file. This writes the header and a blank line, so that at the end `io`
# is positioned at the first byte of the data (if present).

# Note that if you're writing a header for a "detached" data file
# (separate .nhdr and .raw files), `header` should contain a "data file"
# or "datafile" field storing the name of the .raw file.

# Inputs:
# - `version` is a 4-character string, e.g., "0002", giving the NRRD version of the header, or a integer corresponding to a recognized NRRD header version number
# - `header` is a `Dict{String,Any}` of `field=>setting` pairs (as returned by `parse_header`)
# - `keyvals` is a `Dict{String,String}` containing `key=>value` pairs (NRRD0002 or higher, lines like key:=value; many NRRD files do not contain any of these)
# - `comments` is an array containing lines of the header that began with `#` (but with the `#` and leading whitespace stripped out)

# See also: parse_header.
# """
# function write_header(io::IO, version, header, keyvals=nothing, comments=nothing)
#     writeversionstr(io, version)

#     for fn in fieldorder
#         if haskey(header, fn)
#             print(io, fn, ": ")
#             T = get(parse_type, fn, String)
#             nrrd_format(io, T, header[fn])
#             println(io, "")
#         end
#     end
#     if keyvals != nothing
#         for (k,v) in keyvals
#             println(io, k, ":=", v)
#         end
#     end
#     if comments != nothing
#         for c in comments
#             println(io, "# ", c)
#         end
#     end
#     println(io)
#     nothing
# end
# write_header(s::Stream{format"NRRD"}, args...) = write_header(stream(s), args...)

meta_parse(::Type{T}, str) where T = parse(T, str)  # fallback
meta_parse(::Type{String}, str) = str

function meta_parse(::Type{Bool}, str)
    str == "True" && return true
    str == "False" && return false
    error("unrecognized boolean string $str")
end

function meta_parse(::Type{VTuple{T}}, s::AbstractString) where T
    ss = split(s)
    v = Vector{T}(undef, length(ss))
    for i = 1:length(ss)
        v[i] = meta_parse(T, ss[i])
    end
    return v
end

function meta_parse(::Type{Matrix{T}}, s::AbstractString) where T
    v = meta_parse(VTuple{T}, s)
    l = length(v)
    sqrtl = round(Int, sqrt(l))
    sqrtl^2 == l || error("cannot interpret transformation matrix $v")
    reshape(v, sqrtl, sqrtl)
end

meta_format(io, ::Type{T}, x) where T = print(io, x)

function meta_format(io, ::Type{VTuple{T}}, container) where T
    isfirst = true
    for x in container
        if isfirst
            isfirst = false
        else
            print(io, ' ')
        end
        nrrd_format(io, T, x)
    end
    nothing
end

### Utilities

function chksize(sz, sztarget)
    sz == sztarget || error("dimension size should be $sztarget, got $sz")
    nothing
end

function spacings(spacedirections)
    spacings = zeros(length(spacedirections))
    can_convert = true
    d = 1
    for (i, v) in enumerate(spacedirections)
        if v == "none"
            spacings[i] = NaN
        else
            n = 0
            for (j,x) in enumerate(v)
                if x != 0
                    if j == d   # make certain they are in order
                        spacings[i] = x
                        d += 1
                    else
                        can_convert = false
                    end
                    n += 1
                end
            end
            can_convert &= n < 2
        end
    end
    can_convert, spacings
end

### File-related functions

# Adjust the array size if the file is not big enough for reading to succeed
function checked_size(Traw, szraw, sz, iodata)
    cpos = position(iodata)
    datalen = div(filesize(stat(iodata)) - cpos, sizeof(Traw))
    if datalen < prod(szraw)
        if szraw != sz
            # If we've dropped a dimension due to "absorbing"
            # color, etc, don't try to figure out how to correct
            # it, just punt to the user
            error("data length $datalen too small for size $sz array of $T")
        end
        # If the data are smaller than the header suggests, read in as
        # much "complete" data as are available
        sznew = [szraw...]
        strds = [1;cumprod(sznew)]
        k = length(sznew)
        sznew[k] = div(datalen, strds[k])
        while sznew[k] == 0 && k > 1
            pop!(sznew)
            k -= 1
            sznew[k] = div(datalen, strds[k])
        end
        tsznew = (sznew...,)
        warn("header indicates an array size $szraw, but the file size is consistent with at most $tsznew")
        szraw = tsznew
    end
    szraw
end

function stream2name(s::IO)
    name = s.name
    if !startswith(name, "<file ")
        error("io name ", name, " doesn't fit expected pattern")
    end
    name[7:end-1]
end

function find_datafile(iodata::IOStream, header; mode="r", path=nothing)
    fdata = header["ElementDataFile"]
    if path == nothing
        # Check current directory first
        if isfile(fdata)
            iodata = open(fdata)
        else
            path = dirname(stream2name(iodata))
            iodata = open(joinpath(path, fdata), mode)
        end
    else
        iodata = open(joinpath(path, fdata), mode)
    end
    iodata
end

function find_datafile(s::Stream{format"MetaImage"}, header; mode="r")
    find_datafile(stream(s), header; mode=mode)
end

# nrrd_write(io, A::AxisArray) = nrrd_write(io, A.data)
# nrrd_write(io, A::AbstractArray) = nrrd_write_elty(io, A)
# nrrd_write_elty{C<:Colorant}(io, A::AbstractArray{C}) = nrrd_write_elty(io, channelview(A))
# nrrd_write_elty{T<:Fixed}(io, A::AbstractArray{T}) = nrrd_write_elty(io, rawview(A))
# nrrd_write_elty(io, A::AbstractArray) = write(io, A)

end


# conversion between Posit8, Posit16 and Posit32
Posit8(x::Posit16) = ccall((:p16_to_p8, SoftPositPath), Posit8, (Posit16,),x)
Posit32(x::Posit16) = ccall((:p16_to_p32, SoftPositPath), Posit32, (Posit16,),x)
Posit8(x::Posit32) = ccall((:p32_to_p8, SoftPositPath), Posit8, (Posit32,),x)
Posit16(x::Posit32) = ccall((:p32_to_p16, SoftPositPath), Posit16, (Posit32,),x)
Posit16(x::Posit8) = ccall((:p8_to_p16, SoftPositPath), Posit16, (Posit8,),x)
Posit32(x::Posit8) = ccall((:p8_to_p32, SoftPositPath), Posit32, (Posit8,),x)

# conversion from Posit8,16,32 to PX1
Posit8_1(x::Posit8) = ccall((:p8_to_pX1, SoftPositPath), Posit8_1, (Posit8,Int64),x,8)
Posit16_1(x::Posit8) = ccall((:p8_to_pX1, SoftPositPath), Posit16_1, (Posit8,Int64),x,16)
Posit24_1(x::Posit8) = ccall((:p8_to_pX1, SoftPositPath), Posit24_1, (Posit8,Int64),x,24)

Posit8_1(x::Posit16) = ccall((:p16_to_pX1, SoftPositPath), Posit8_1, (Posit16,Int64),x,8)
Posit16_1(x::Posit16) = ccall((:p16_to_pX1, SoftPositPath), Posit16_1, (Posit16,Int64),x,16)
Posit24_1(x::Posit16) = ccall((:p16_to_pX1, SoftPositPath), Posit24_1, (Posit16,Int64),x,24)

Posit8_1(x::Posit32) = ccall((:p32_to_pX1, SoftPositPath), Posit8_1, (Posit32,Int64),x,8)
Posit16_1(x::Posit32) = ccall((:p32_to_pX1, SoftPositPath), Posit16_1, (Posit32,Int64),x,16)
Posit24_1(x::Posit32) = ccall((:p32_to_pX1, SoftPositPath), Posit24_1, (Posit32,Int64),x,24)

# conversion from Posit8,16,32 to PX2
Posit8_2(x::Posit8) = ccall((:p8_to_pX2, SoftPositPath), Posit8_2, (Posit8,Int64),x,8)
Posit16_2(x::Posit8) = ccall((:p8_to_pX2, SoftPositPath), Posit16_2, (Posit8,Int64),x,16)
Posit24_2(x::Posit8) = ccall((:p8_to_pX2, SoftPositPath), Posit24_2, (Posit8,Int64),x,24)

Posit8_2(x::Posit16) = ccall((:p16_to_pX2, SoftPositPath), Posit8_2, (Posit16,Int64),x,8)
Posit16_2(x::Posit16) = ccall((:p16_to_pX2, SoftPositPath), Posit16_2, (Posit16,Int64),x,16)
Posit24_2(x::Posit16) = ccall((:p16_to_pX2, SoftPositPath), Posit24_2, (Posit16,Int64),x,24)

Posit8_2(x::Posit32) = ccall((:p32_to_pX2, SoftPositPath), Posit8_2, (Posit32,Int64),x,8)
Posit16_2(x::Posit32) = ccall((:p32_to_pX2, SoftPositPath), Posit16_2, (Posit32,Int64),x,16)
Posit24_2(x::Posit32) = ccall((:p32_to_pX2, SoftPositPath), Posit24_2, (Posit32,Int64),x,24)

# conversion from PX1 to Posit8,16,32
Posit8(x::Posit8_1) = ccall((:pX1_to_p8, SoftPositPath), Posit8, (Posit8_1,Int64),x,8)
Posit8(x::Posit16_1) = ccall((:pX1_to_p8, SoftPositPath), Posit8, (Posit16_1,Int64),x,16)
Posit8(x::Posit24_1) = ccall((:pX1_to_p8, SoftPositPath), Posit8, (Posit24_1,Int64),x,24)

Posit16(x::Posit8_1) = ccall((:pX1_to_p16, SoftPositPath), Posit16, (Posit8_1,Int64),x,8)
Posit16(x::Posit16_1) = ccall((:pX1_to_p16, SoftPositPath), Posit16, (Posit16_1,Int64),x,16)
Posit16(x::Posit24_1) = ccall((:pX1_to_p16, SoftPositPath), Posit16, (Posit24_1,Int64),x,24)

Posit32(x::Posit8_1) = ccall((:pX1_to_p32, SoftPositPath), Posit32, (Posit8_1,Int64),x,8)
Posit32(x::Posit16_1) = ccall((:pX1_to_p32, SoftPositPath), Posit32, (Posit16_1,Int64),x,16)
Posit32(x::Posit24_1) = ccall((:pX1_to_p32, SoftPositPath), Posit32, (Posit24_1,Int64),x,24)

# conversion from PX2 to Posit8,16,32
Posit8(x::Posit8_2) = ccall((:pX2_to_p8, SoftPositPath), Posit8, (Posit8_2,Int64),x,8)
Posit8(x::Posit16_2) = ccall((:pX2_to_p8, SoftPositPath), Posit8, (Posit16_2,Int64),x,16)
Posit8(x::Posit24_2) = ccall((:pX2_to_p8, SoftPositPath), Posit8, (Posit24_2,Int64),x,24)

Posit16(x::Posit8_2) = ccall((:pX2_to_p16, SoftPositPath), Posit16, (Posit8_2,Int64),x,8)
Posit16(x::Posit16_2) = ccall((:pX2_to_p16, SoftPositPath), Posit16, (Posit16_2,Int64),x,16)
Posit16(x::Posit24_2) = ccall((:pX2_to_p16, SoftPositPath), Posit16, (Posit24_2,Int64),x,24)

Posit32(x::Posit8_2) = ccall((:pX2_to_p32, SoftPositPath), Posit32, (Posit8_2,Int64),x,8)
Posit32(x::Posit16_2) = ccall((:pX2_to_p32, SoftPositPath), Posit32, (Posit16_2,Int64),x,16)
Posit32(x::Posit24_2) = ccall((:pX2_to_p32, SoftPositPath), Posit32, (Posit24_2,Int64),x,24)

# conversion between PX1 and PX2
Posit8_1(x::Posit8_2) = ccall((:pX2_to_pX1, SoftPositPath), Posit8_1, (Posit8_2,Int64),x,8)
Posit8_2(x::Posit8_1) = ccall((:pX1_to_pX2, SoftPositPath), Posit8_2, (Posit8_1,Int64),x,8)

Posit16_1(x::Posit16_2) = ccall((:pX2_to_pX1, SoftPositPath), Posit16_1, (Posit16_2,Int64),x,16)
Posit16_2(x::Posit16_1) = ccall((:pX1_to_pX2, SoftPositPath), Posit16_2, (Posit16_1,Int64),x,16)

Posit24_1(x::Posit24_2) = ccall((:pX2_to_pX1, SoftPositPath), Posit24_1, (Posit24_2,Int64),x,24)
Posit24_2(x::Posit24_1) = ccall((:pX1_to_pX2, SoftPositPath), Posit24_2, (Posit24_1,Int64),x,24)


module ArrayAllez

include("cache.jl")

include("threads.jl")

include("inplace.jl")

@init @require Tracker = "9f7883ad-71c0-57eb-9f7f-b5c9e6d3789c" begin
	include("inplace-flux.jl")
	include("prod+cumprod.jl")
end
include("inplace-zygote.jl")

include("dropdims.jl")

end


# Check for errors before they happen
function test_option_configuration(T, options::Options)
    for op in (options.binops..., options.unaops...)
        if is_anonymous_function(op)
            throw(
                AssertionError(
                    "Anonymous functions can't be used as operators for SymbolicRegression.jl",
                ),
            )
        end
    end

    test_input = map(x -> convert(T, x), LinRange(-100, 100, 99))
    cur_op = nothing
    try
        for left in test_input
            for right in test_input
                for binop in options.binops
                    cur_op = binop
                    test_output = binop.(left, right)
                end
            end
            for unaop in options.unaops
                cur_op = unaop
                test_output = unaop.(left)
            end
        end
    catch error
        throw(
            AssertionError(
                "Your configuration is invalid - one of your operators ($cur_op) is not well-defined over the real line.",
            ),
        )
    end

    for binop in options.binops
        if binop in options.unaops
            throw(
                AssertionError(
                    "Your configuration is invalid - one operator ($binop) appears in both the binary operators and unary operators.",
                ),
            )
        end
    end
end

# Check for errors before they happen
function test_dataset_configuration(dataset::Dataset{T}, options::Options) where {T<:Real}
    n = dataset.n
    if n != size(dataset.X, 2) || n != size(dataset.y, 1)
        throw(
            AssertionError(
                "Dataset dimensions are invalid. Make sure X is of shape [features, rows], y is of shape [rows] and if there are weights, they are of shape [rows].",
            ),
        )
    end

    if size(dataset.X, 2) > 10000
        if !options.batching
            debug(
                (options.verbosity > 0 || options.progress),
                "Note: you are running with more than 10,000 datapoints. You should consider turning on batching (`options.batching`), and also if you need that many datapoints. Unless you have a large amount of noise (in which case you should smooth your dataset first), generally < 10,000 datapoints is enough to find a functional form.",
            )
        end
    end

    if !(typeof(options.loss) <: SupervisedLoss)
        if dataset.weighted
            if !(3 in [m.nargs - 1 for m in methods(options.loss)])
                throw(
                    AssertionError(
                        "When you create a custom loss function, and are using weights, you need to define your loss function with three scalar arguments: f(prediction, target, weight).",
                    ),
                )
            end
        end
    end
end

""" Move custom operators and loss functions to workers, if undefined """
function move_functions_to_workers(procs, options::Options, dataset::Dataset{T}) where {T}
    for function_set in 1:6
        if function_set == 1
            ops = options.unaops
            nargs = 1
        elseif function_set == 2
            ops = options.binops
            nargs = 2
        elseif function_set == 3
            if !options.enable_autodiff
                continue
            end
            ops = options.diff_unaops
            nargs = 1
        elseif function_set == 4
            if !options.enable_autodiff
                continue
            end
            ops = options.diff_binops
            nargs = 2
        elseif function_set == 5
            if typeof(options.loss) <: SupervisedLoss
                continue
            end
            ops = (options.loss,)
            nargs = dataset.weighted ? 3 : 2
        elseif function_set == 6
            if !(typeof(options.earlyStopCondition) <: Function)
                continue
            end
            ops = (options.earlyStopCondition,)
            nargs = 2
        end
        for op in ops
            try
                test_function_on_workers(T, nargs, op, procs)
            catch e
                undefined_on_workers = isa(e.captured.ex, UndefVarError)
                if undefined_on_workers
                    copy_definition_to_workers(op, procs, options)
                else
                    throw(e)
                end
            end
            test_function_on_workers(T, nargs, op, procs)
        end
    end
end

function copy_definition_to_workers(op, procs, options::Options)
    name = nameof(op)
    debug_inline(
        (options.verbosity > 0 || options.progress),
        "Copying definition of $op to workers...",
    )
    src_ms = methods(op).ms
    # Thanks https://discourse.julialang.org/t/easy-way-to-send-custom-function-to-distributed-workers/22118/2
    @everywhere procs @eval function $name end
    for m in src_ms
        @everywhere procs @eval $m
    end
    return debug((options.verbosity > 0 || options.progress), "Finished!")
end

function test_function_on_workers(T, nargs, op, procs)
    futures = []
    for proc in procs
        if nargs == 1
            push!(futures, @spawnat proc op(convert(T, 0)))
        elseif nargs == 2 #2D ops, and loss function
            push!(futures, @spawnat proc op(convert(T, 0), convert(T, 0)))
        elseif nargs == 3 #weighted loss function
            push!(futures, @spawnat proc op(convert(T, 0), convert(T, 0), convert(T, 0)))
        end
    end
    for future in futures
        fetch(future)
    end
end

function activate_env_on_workers(procs, project_path::String, options::Options)
    debug((options.verbosity > 0 || options.progress), "Activating environment on workers.")
    @everywhere procs begin
        Base.MainInclude.eval(
            quote
                using Pkg
                Pkg.activate($$project_path)
            end,
        )
    end
end

function import_module_on_workers(procs, filename::String, options::Options)
    included_local = !("SymbolicRegression" in [k.name for (k, v) in Base.loaded_modules])
    if included_local
        debug_inline(
            (options.verbosity > 0 || options.progress),
            "Importing local module ($filename) on workers...",
        )
        @everywhere procs begin
            # Parse functions on every worker node
            Base.MainInclude.eval(
                quote
                    include($$filename)
                    using .SymbolicRegression
                end,
            )
        end
        debug((options.verbosity > 0 || options.progress), "Finished!")
    else
        debug_inline(
            (options.verbosity > 0 || options.progress),
            "Importing installed module on workers...",
        )
        @everywhere procs begin
            Base.MainInclude.eval(using SymbolicRegression)
        end
        debug((options.verbosity > 0 || options.progress), "Finished!")
    end
end

function test_module_on_workers(procs, options::Options)
    debug_inline(
        (options.verbosity > 0 || options.progress), "Testing module on workers..."
    )
    futures = []
    for proc in procs
        push!(futures, @spawnat proc SymbolicRegression.gen_random_tree(3, options, 5))
    end
    for future in futures
        fetch(future)
    end
    return debug((options.verbosity > 0 || options.progress), "Finished!")
end

function test_entire_pipeline(procs, dataset::Dataset{T}, options::Options) where {T<:Real}
    futures = []
    debug_inline(
        (options.verbosity > 0 || options.progress), "Testing entire pipeline on workers..."
    )
    for proc in procs
        push!(
            futures,
            @spawnat proc begin
                tmp_pop = Population(
                    dataset,
                    convert(T, 1);
                    npop=20,
                    nlength=3,
                    options=options,
                    nfeatures=dataset.nfeatures,
                )
                tmp_pop = s_r_cycle(
                    dataset,
                    convert(T, 1),
                    tmp_pop,
                    5,
                    5,
                    ones(T, options.maxsize);
                    verbosity=options.verbosity,
                    options=options,
                    record=RecordType(),
                )[1]
                tmp_pop = optimize_and_simplify_population(
                    dataset, T(1.0), tmp_pop, options, options.maxsize, RecordType()
                )
            end
        )
    end
    for future in futures
        fetch(future)
    end
    return debug((options.verbosity > 0 || options.progress), "Finished!")
end


using Plots, Dates; gr();

export qwckplot

##  qwckplot function
#
#examples:
#  GCMGridLoad()
#  qwckplot(MeshArrays.Depth)
#  qwckplot(MeshArrays.Depth,"Ocean Bottom Depth")

"""
    qwckplot(fld::gcmfaces)

Call qwckplot(fld::gcmfaces,ttl::String) with current date for title
"""
function qwckplot(fld::gcmfaces)
    tmp1=Dates.now()
    tmp1=Dates.format(tmp1, "yyyy-mm-dd HH:MM:SS")
    qwckplot(fld,"Plotted at time "*tmp1)
end

"""
    qwckplot(fld::gcmfaces,ttl::String)

Plot input using convert2array and heatmap + add title
"""
function qwckplot(fld::gcmfaces,ttl::String)
    arr=convert2array(fld)
    arr=permutedims(arr,[2 1])
    #This uses Plots.jl:
    p=heatmap(arr,title=ttl)
end


module PATH

import MathOptInterface
import SparseArrays

include(joinpath(dirname(@__DIR__), "deps", "deps.jl"))

include("C_API.jl")
include("MOI_wrapper.jl")

end