export DryModel, EquilMoist

#### Moisture component in atmosphere model
abstract type MoistureModel end

vars_state(::MoistureModel, FT) = @vars()
vars_gradient(::MoistureModel, FT) = @vars()
vars_diffusive(::MoistureModel, FT) = @vars()
vars_aux(::MoistureModel, FT) = @vars()

function atmos_nodal_update_aux!(::MoistureModel, m::AtmosModel, state::Vars,
                                 aux::Vars, t::Real)
end
function flux_moisture!(::MoistureModel, ::AtmosModel, flux::Grad, state::Vars, aux::Vars, t::Real)
end
function diffusive!(::MoistureModel, diffusive, ∇transform, state, aux, t)
end
function flux_diffusive!(::MoistureModel, flux::Grad, state::Vars, diffusive::Vars, aux::Vars, t::Real, D_t)
end
function flux_nondiffusive!(::MoistureModel, flux::Grad, state::Vars, aux::Vars, t::Real)
end
function gradvariables!(::MoistureModel, transform::Vars, state::Vars, aux::Vars, t::Real)
end

@inline function internal_energy(moist::MoistureModel, orientation::Orientation, state::Vars, aux::Vars)
  MoistThermodynamics.internal_energy(state.ρ, state.ρe, state.ρu, gravitational_potential(orientation, aux))
end
@inline temperature(moist::MoistureModel, orientation::Orientation, state::Vars, aux::Vars) = air_temperature(thermo_state(moist, orientation, state, aux))
@inline pressure(moist::MoistureModel, orientation::Orientation, state::Vars, aux::Vars) = air_pressure(thermo_state(moist, orientation, state, aux))
@inline soundspeed(moist::MoistureModel, orientation::Orientation, state::Vars, aux::Vars) = soundspeed_air(thermo_state(moist, orientation, state, aux))

@inline function total_specific_enthalpy(moist::MoistureModel, orientation::Orientation, state::Vars, aux::Vars)
  phase = thermo_state(moist, orientation, state, aux)
  R_m = gas_constant_air(phase)
  T = air_temperature(phase)
  e_tot = state.ρe * (1/state.ρ)
  e_tot + R_m*T
end

"""
    DryModel

Assumes the moisture components is in the dry limit.
"""
struct DryModel <: MoistureModel
end

vars_aux(::DryModel,FT) = @vars(θ_v::FT)
@inline function atmos_nodal_update_aux!(moist::DryModel, atmos::AtmosModel,
                                         state::Vars, aux::Vars, t::Real)
  e_int = internal_energy(moist, atmos.orientation, state, aux)
  TS = PhaseDry(e_int, state.ρ)
  aux.moisture.θ_v = virtual_pottemp(TS)
  nothing
end

thermo_state(moist::DryModel, orientation::Orientation, state::Vars, aux::Vars) = PhaseDry(internal_energy(moist, orientation, state, aux), state.ρ)

"""
    EquilMoist

Assumes the moisture components are computed via thermodynamic equilibrium.
"""
Base.@kwdef struct EquilMoist <: MoistureModel
  maxiter::Int = 3
end
vars_state(::EquilMoist,FT) = @vars(ρq_tot::FT)
vars_gradient(::EquilMoist,FT) = @vars(q_tot::FT, h_tot::FT)
vars_diffusive(::EquilMoist,FT) = @vars(∇q_tot::SVector{3,FT})
vars_aux(::EquilMoist,FT) = @vars(temperature::FT, θ_v::FT, q_liq::FT)

@inline function atmos_nodal_update_aux!(moist::EquilMoist, atmos::AtmosModel,
                                         state::Vars, aux::Vars, t::Real)
  e_int = internal_energy(moist, atmos.orientation, state, aux)
  TS = PhaseEquil(e_int, state.ρ, state.moisture.ρq_tot/state.ρ, moist.maxiter)
  aux.moisture.temperature = air_temperature(TS)
  aux.moisture.θ_v = virtual_pottemp(TS)
  aux.moisture.q_liq = PhasePartition(TS).liq
  nothing
end

function thermo_state(moist::EquilMoist, orientation::Orientation, state::Vars, aux::Vars)
  e_int = internal_energy(moist, orientation, state, aux)
  FT = eltype(state)
  return PhaseEquil{FT}(e_int, state.ρ, state.moisture.ρq_tot/state.ρ, aux.moisture.temperature)
end

function gradvariables!(moist::EquilMoist, transform::Vars, state::Vars, aux::Vars, t::Real)
  ρinv = 1/state.ρ
  transform.moisture.q_tot = state.moisture.ρq_tot * ρinv
end

function diffusive!(moist::EquilMoist, diffusive::Vars, ∇transform::Grad, state::Vars, aux::Vars, t::Real)
  # diffusive flux of q_tot
  diffusive.moisture.∇q_tot = ∇transform.moisture.q_tot
end

function flux_moisture!(moist::EquilMoist, atmos::AtmosModel, flux::Grad, state::Vars, aux::Vars, t::Real)
  ρ = state.ρ
  u = state.ρu / ρ
  flux.moisture.ρq_tot += u * state.moisture.ρq_tot
end

function flux_diffusive!(moist::EquilMoist, flux::Grad, state::Vars, diffusive::Vars, aux::Vars, t::Real, D_t)
  d_q_tot = (-D_t) .* diffusive.moisture.∇q_tot
  flux_diffusive!(moist, flux, state, d_q_tot)
end
#TODO: Consider whether to not pass ρ and ρu (not state), foc BCs reasons
function flux_diffusive!(moist::EquilMoist, flux::Grad, state::Vars, d_q_tot)
  flux.ρ += d_q_tot * state.ρ
  flux.ρu += d_q_tot .* state.ρu'
  flux.moisture.ρq_tot += d_q_tot * state.ρ
end


module CUSOLVER

import CUDAdrv: CUDAdrv, CuContext, CuStream_t, CuPtr, PtrOrCuPtr, CU_NULL
import CUDAapi

using ..CuArrays
using ..CuArrays: libcusolver, active_context, _getindex, unsafe_free!

using LinearAlgebra
using SparseArrays

import Base.one
import Base.zero
import CuArrays.CUSPARSE.CuSparseMatrixCSR
import CuArrays.CUSPARSE.CuSparseMatrixCSC
import CuArrays.CUSPARSE.cusparseMatDescr_t

include("libcusolver_types.jl")
include("error.jl")

const _dense_handles = Dict{CuContext,cusolverDnHandle_t}()
const _dense_handle = Ref{cusolverDnHandle_t}(C_NULL)
const _sparse_handles = Dict{CuContext,cusolverSpHandle_t}()
const _sparse_handle = Ref{cusolverSpHandle_t}(C_NULL)

function dense_handle()
    if _dense_handle[] == C_NULL
        @assert isassigned(active_context) # some other call should have initialized CUDA
        _dense_handle[] = get!(_dense_handles, active_context[]) do
            context = active_context[]
            handle = cusolverDnCreate()
            atexit(()->CUDAdrv.isvalid(context) && cusolverDnDestroy(handle))
            handle
        end
    end
    return _dense_handle[]
end

function sparse_handle()
    if _sparse_handle[] == C_NULL
        @assert isassigned(active_context) # some other call should have initialized CUDA
        _sparse_handle[] = get!(_sparse_handles, active_context[]) do
            context = active_context[]
            handle = cusolverSpCreate()
            atexit(()->CUDAdrv.isvalid(context) && cusolverSpDestroy(handle))
            handle
        end
    end
    return _sparse_handle[]
end

include("libcusolver.jl")
include("sparse.jl")
include("dense.jl")
include("highlevel.jl")

version() = VersionNumber(cusolverGetProperty(CUDAapi.MAJOR_VERSION),
                          cusolverGetProperty(CUDAapi.MINOR_VERSION),
                          cusolverGetProperty(CUDAapi.PATCH_LEVEL))

end


export setindex

struct SetIndex{T,N} <: ArrayOp{T,N}
    input::ArrayOp{T,N}
    idx::Tuple
    val
end

function setindex(x::ArrayOp, val, idxs...)
    SetIndex(x, idxs, val)
end

function stage(ctx, sidx::SetIndex)
    inp = cached_stage(ctx, sidx.input)

    dmn = domain(inp)
    idxs = [if isa(sidx.idx[i], Colon)
        indexes(dmn)[i]
    else
        sidx.idx[i]
    end for i in 1:length(sidx.idx)]

    ps = Array{Any}(size(chunks(inp)))
    ps[:] = chunks(inp)
    subdmns = domainchunks(inp)
    d = ArrayDomain(idxs)

    groups = map(group_indices, subdmns.cumlength, indexes(d))
    sz = map(length, groups)
    pieces = Array{Union{Chunk, Thunk}}(sz)
    for i = CartesianRange(sz)
        idx_and_dmn = map(getindex, groups, i.I)
        idx = map(x->x[1], idx_and_dmn)
        local_dmn = ArrayDomain(map(x->x[2], idx_and_dmn))
        s = subdmns[idx...]
        part_to_set = sidx.val
        ps[idx...] = Thunk(ps[idx...]) do p
            q = copy(p)
            q[indexes(project(s, local_dmn))...] = part_to_set
            q
        end
    end
    inp.chunks = ps
    inp
end


## ExoplanetsSysSim/examples/setup_abc.jl
## (c) 2015 Eric B. Ford

# Add "minimum ABC dist skip pass 2"

module EvalSysSimModel
  export setup, get_param_vector, get_ss_obs #, evaluate_model
  export gen_data, calc_summary_stats, calc_distance, is_valid
  using ExoplanetsSysSim

  sim_param_closure = SimParam()
  summary_stat_ref_closure =  CatalogSummaryStatistics()

  # If we just wanted one call to evaluate_model, we could just use the macro
  #include("eval_model_macro.jl")
  #@make_evaluate_model(sim_param_closure,cat_phys_try_closure,cat_obs_try_closure,summary_stat_try_closure,summary_stat_ref_closure)
  # But we want a little finer control, so we'll do it manually....

    function is_valid(param_vector::Vector{Float64})
      global sim_param_closure
      update_sim_param_from_vector!(param_vector,sim_param_closure)
      const eta_pl = exp(get_real(sim_param_closure,"log_eta_pl"))
      const sigma_log_r = get_real(sim_param_closure,"sigma_log_planet_radius")
      const sigma_log_P = get_real(sim_param_closure,"sigma_log_planet_period")
      if   eta_pl <=0. || sigma_log_r <= 0. || sigma_log_P<=0.
         return false
      end
      return true
    end

    function gen_data(param_vector::Vector{Float64})
      global sim_param_closure
      update_sim_param_from_vector!(param_vector,sim_param_closure)
      cat_phys = generate_kepler_physical_catalog(sim_param_closure)
      cat_obs = observe_kepler_targets_single_obs(cat_phys,sim_param_closure)
      (cat_obs,cat_phys)
    end

    # TODO OPT: Eventually, could adapt ABC.jl to use distance from first pass to decide if should compute additional summary statistics
    function calc_summary_stats(cat::Tuple{KeplerObsCatalog,KeplerPhysicalCatalog} )
      global sim_param_closure
      # cat_obs = cat[1]
      # cat_phys = cat[2]
      sum_stat = calc_summary_stats_sim_pass_one_demo(cat[1],cat[2],sim_param_closure)
      sum_stat = calc_summary_stats_sim_pass_two_demo(cat[1],cat[2],sum_stat,sim_param_closure)
    end

    function calc_distance(sum_stat_obs::CatalogSummaryStatistics,sum_stat_sim::CatalogSummaryStatistics, n::Integer = 0)
      global sim_param_closure
      dist1 = calc_distance_vector_demo(sum_stat_obs,sum_stat_sim, 1, sim_param_closure)
      if haskey(sim_param_closure,"minimum ABC dist skip pass 2")
         if calc_scalar_distance(dist1) > get_real(sim_param_closure,"minimum ABC dist skip pass 2")
            return dist1
         end
      end
      dist2 = calc_distance_vector_demo(sum_stat_obs,sum_stat_sim, 2, sim_param_closure)
      num_available = length(dist1)+length(dist2)
      num_to_use = n>0 ? min(n,num_available) : num_available
      return calc_scalar_distance([dist1; dist2][1:num_to_use])
    end

  function setup()
    global sim_param_closure = setup_sim_param_demo()
    # To keep the abc demo runtime reasonable, use a modest number of target
    add_param_fixed(sim_param_closure,"num_targets_sim_pass_one",1900)  # For faster simulated catalogs
    add_param_fixed(sim_param_closure,"num_kepler_targets",190000)  # For "observed" catalog
    # Since we haven't parallelized the reading of stellar catalog files, the abc_demo uses made up targets, allowing us to run on multiple processors
    add_param_fixed(sim_param_closure,"generate_kepler_target",ExoplanetsSysSim.generate_kepler_target_simple)

    # cat_phys = generate_kepler_physical_catalog(sim_param_closure)
    # cat_obs = observe_kepler_targets_single_obs(cat_phys,sim_param_closure)
    cat_obs = simulated_read_kepler_observations(sim_param_closure)
    global summary_stat_ref_closure = calc_summary_stats_obs_demo(cat_obs,sim_param_closure)
  end

  get_param_vector() = make_vector_of_sim_param(sim_param_closure)
  get_ss_obs() = summary_stat_ref_closure

end  # module EvalSysSimModel

module SysSimABC
  export setup_abc, run_abc
  import ABC
  import Distributions
  import ExoplanetsSysSim
  import EvalSysSimModel

  function setup_abc(num_dist::Integer = 0)
    global sim_param_closure
    EvalSysSimModel.setup()
    println("# setup_abc: ",EvalSysSimModel.sim_param_closure)
    theta_true = EvalSysSimModel.get_param_vector()
    param_prior = Distributions.MvNormal(theta_true,ones(length(theta_true)))
    in_parallel = nworkers() > 1 ? true : false

    calc_distance_ltd(sum_stat_obs::ExoplanetsSysSim.CatalogSummaryStatistics,sum_stat_sim::ExoplanetsSysSim.CatalogSummaryStatistics) = EvalSysSimModel.calc_distance(sum_stat_obs,sum_stat_sim,num_dist)

    abc_plan = ABC.abc_pmc_plan_type(EvalSysSimModel.gen_data,EvalSysSimModel.calc_summary_stats,calc_distance_ltd, param_prior, is_valid=EvalSysSimModel.is_valid,
                                     num_part=40, num_max_attempt=100, num_max_times=5, epsilon_init=9.9e99, target_epsilon=0.001, in_parallel=in_parallel);
  end

  function run_abc(abc_plan::ABC.abc_pmc_plan_type)
    #global sim_param_closure
    println("# run_abc: ",EvalSysSimModel.sim_param_closure)
    ss_true = EvalSysSimModel.get_ss_obs()
    pop_out = ABC.run_abc(abc_plan,ss_true;verbose=true)
  end

  function run_abc(abc_plan::ABC.abc_pmc_plan_type, pop::ABC.abc_population_type)
    #global sim_param_closure
    dist_threshold = maximum(pop.dist)
    ExoplanetsSysSim.add_param_fixed(EvalSysSimModel.sim_param_closure,"minimum ABC dist skip pass 2",dist_threshold)
    println("# run_abc: ",EvalSysSimModel.sim_param_closure)
    ss_true = EvalSysSimModel.get_ss_obs()
    pop_out = ABC.run_abc(abc_plan,ss_true,pop;verbose=true)
  end

end # module SysSimABC


function FieldShortPeriodicsInterpolatedCoefficient(arg0::jint)
    return FieldShortPeriodicsInterpolatedCoefficient((jint,), arg0)
end

function add_grid_point(obj::FieldShortPeriodicsInterpolatedCoefficient, arg0::FieldAbsoluteDate, arg1::Vector{RealFieldElement})
    return jcall(obj, "addGridPoint", void, (FieldAbsoluteDate, Vector{RealFieldElement}), arg0, arg1)
end

function clear_history(obj::FieldShortPeriodicsInterpolatedCoefficient)
    return jcall(obj, "clearHistory", void, ())
end

function value(obj::FieldShortPeriodicsInterpolatedCoefficient, arg0::FieldAbsoluteDate)
    return jcall(obj, "value", Vector{RealFieldElement}, (FieldAbsoluteDate,), arg0)
end


import AQFED.Random
import AQFED
import Random123
import Random: MersenneTwister

function simulation(r, n)
    spot = 1.0
    vol = 1.0
    strike = 1.0
    sum = 0.0
    sqrttte = 1.0
    @simd for i = 1:n
        #z = randn(r, Float64)
        u = rand(r,Float64)
    z = AQFED.Math.norminv(u) #faster than with AS241, esp when combined with exp call.
        sum += max(spot * exp(vol * z * sqrttte - 0.5 * (vol * sqrttte)^2) - strike, 0)
    end
    return sum / n
end

function simsum(r, n)
    u = Vector{Float64}(undef, n)
    rand!(r, u)
    return sum(u)
end

@testset "BlackSim" begin
    refValue = AQFED.Black.blackScholesFormula(true, 1.0, 1.0, 1.0, 1.0, 1.0)
    gens = [
        MersenneTwister(20130129),
        AQFED.Random.MersenneTwister64(UInt64(20130129)),
        AQFED.Random.Well1024a(UInt32(20130129)),
        AQFED.Random.Mixmax17(UInt64(20130129)),
        AQFED.Random.Mixmax240(UInt64(20130129)),
        AQFED.Random.MRG63k3a(),
        Random123.Philox4x(UInt64, (20130129, 20100921), 10),
        AQFED.Random.Chacha8SIMD(),
        AQFED.Random.Blabla8(),
    ]
    for gen in gens
        value = simulation(gen, 64 * 1024)
        println(typeof(gen), " ", value, " ", refValue, " ", value - refValue)
        @test isapprox(refValue, value, atol = 1e-2)
    end
end

@testset "MT64FirstNumbers" begin
    init = UInt64.([0x12345, 0x23456, 0x34567, 0x45678])
    gen = AQFED.Random.MersenneTwister64(init)
    println("1000 outputs of UInt64")
    for i = 1:1000
        ui = rand(gen, UInt64)
        # print(" " ,ui)
        # if (i % 5 == 4)
        #     println()
        # end
    end
    #numbers comming from reference C implementation
    referenceNumbers = [
        0.35252031,
        0.51052342,
        0.79771733,
        0.39300273,
        0.27216673,
        0.72151068,
        0.43144703,
        0.38522290,
        0.20270676,
        0.58227313,
        0.80812143,
        0.83767297,
        0.92401619,
        0.84065425,
        0.00852052,
        0.13975395,
        0.35250930,
        0.71196972,
        0.14627395,
        0.17775331,
        0.61046382,
        0.49623272,
        0.23292425,
        0.25038837,
        0.04380664,
        0.43275994,
        0.74540936,
        0.33830700,
        0.68832616,
        0.68744230,
        0.63626548,
        0.85932936,
        0.37089670,
        0.50756304,
        0.69925960,
        0.83481025,
        0.09053196,
        0.09523253,
        0.17783108,
        0.78027239,
        0.70071054,
        0.51879252,
        0.83027285,
        0.92895011,
        0.72144803,
        0.18868644,
        0.83655674,
        0.20358945,
        0.99852143,
        0.88340103,
        0.46729949,
        0.96993433,
        0.00162682,
        0.46829774,
        0.59080423,
        0.54921999,
        0.42516462,
        0.54952196,
        0.99534722,
        0.04473888,
        0.71139235,
        0.91881407,
        0.33781561,
        0.45746234,
        0.78292126,
        0.69206723,
        0.66175448,
        0.07091147,
        0.18179208,
        0.38168454,
        0.38819527,
        0.42452711,
        0.22732724,
        0.16191307,
        0.36842667,
        0.13060083,
        0.68833248,
        0.60498705,
        0.19195304,
        0.26628584,
        0.17030858,
        0.23892426,
        0.38430236,
        0.28034283,
        0.76069020,
        0.21560653,
        0.78101667,
        0.90847812,
        0.06467974,
        0.18487868,
        0.23570471,
        0.29475460,
        0.65563767,
        0.10066446,
        0.57272419,
        0.88731391,
        0.60650995,
        0.96346079,
        0.32940100,
        0.29977746,
        0.03798193,
        0.18026822,
        0.22402746,
        0.45480119,
        0.98114604,
        0.25800668,
        0.94362433,
        0.17901062,
        0.36019313,
        0.45933644,
        0.68309457,
        0.28175454,
        0.00774729,
        0.77054527,
        0.99723413,
        0.59807532,
        0.10294164,
        0.32429228,
        0.54928986,
        0.18410980,
        0.08441555,
        0.14230333,
        0.58892064,
        0.94030475,
        0.35378784,
        0.77584320,
        0.71222448,
        0.83565208,
        0.47309248,
        0.23810761,
        0.74408520,
        0.08891527,
        0.09729786,
        0.38377368,
        0.05092308,
        0.69065638,
        0.10449489,
        0.45050670,
        0.92209534,
        0.80083714,
        0.27902692,
        0.26897142,
        0.50650468,
        0.80111472,
        0.54590012,
        0.96406097,
        0.63779553,
        0.81054357,
        0.75369248,
        0.47473037,
        0.89100315,
        0.89395984,
        0.09985519,
        0.34087631,
        0.22293557,
        0.24375510,
        0.31764191,
        0.04076993,
        0.06160830,
        0.41333434,
        0.11883030,
        0.04548820,
        0.01008040,
        0.25336184,
        0.07325432,
        0.49860151,
        0.07148695,
        0.89483338,
        0.87054457,
        0.15116809,
        0.59650469,
        0.47487776,
        0.43490298,
        0.36684681,
        0.16470796,
        0.76865078,
        0.42920071,
        0.20545481,
        0.87615922,
        0.80332404,
        0.36462506,
        0.49571309,
        0.51904488,
        0.15534589,
        0.43719893,
        0.16562157,
        0.37290862,
        0.91842631,
        0.21310523,
        0.87849154,
        0.18532269,
        0.81713354,
        0.52182344,
        0.51845619,
        0.96261204,
        0.18758718,
        0.68897600,
        0.61484764,
        0.46752993,
        0.05865458,
        0.11614359,
        0.90386866,
        0.45781805,
        0.70649579,
        0.50917048,
        0.21210656,
        0.97818608,
        0.00788342,
        0.61375222,
        0.67366318,
        0.24197878,
        0.66177985,
        0.10463932,
        0.67390799,
        0.50025262,
        0.88332650,
        0.77966851,
        0.13403622,
        0.54357114,
        0.97664854,
        0.06540961,
        0.24013176,
        0.67234032,
        0.91347883,
        0.35486839,
        0.87207865,
        0.43036581,
        0.23652488,
        0.81238450,
        0.72058432,
        0.42239916,
        0.80265764,
        0.03552838,
        0.61939480,
        0.50972420,
        0.21053832,
        0.59952743,
        0.36821802,
        0.45659617,
        0.12529468,
        0.76941623,
        0.99878168,
        0.08602783,
        0.81825937,
        0.39350710,
        0.86090923,
        0.36090230,
        0.75628888,
        0.45036982,
        0.44602266,
        0.20595631,
        0.62241953,
        0.36777732,
        0.47523727,
        0.50248178,
        0.73570362,
        0.48237781,
        0.45590948,
        0.73580783,
        0.96403851,
        0.94586342,
        0.48819868,
        0.48102038,
        0.94618182,
        0.90279924,
        0.78396650,
        0.85182389,
        0.92149394,
        0.32679198,
        0.83554856,
        0.28320609,
        0.34598409,
        0.82090005,
        0.40177958,
        0.38888785,
        0.77873931,
        0.23297931,
        0.75329335,
        0.30770340,
        0.71417540,
        0.68939065,
        0.36577776,
        0.50784857,
        0.50928090,
        0.02552055,
        0.85999075,
        0.26692089,
        0.01402799,
        0.67550392,
        0.48305605,
        0.74608351,
        0.63408891,
        0.58904230,
        0.44337996,
        0.42174728,
        0.74041679,
        0.72719148,
        0.19801992,
        0.66263633,
        0.10381594,
        0.32818760,
        0.68369661,
        0.56076212,
        0.68681921,
        0.91616269,
        0.39836106,
        0.39685027,
        0.97507945,
        0.91010563,
        0.27447360,
        0.95538357,
        0.76758522,
        0.60091060,
        0.37734461,
        0.82948248,
        0.06598078,
        0.50147615,
        0.08417763,
        0.18910044,
        0.51661735,
        0.55011011,
        0.64888175,
        0.82986845,
        0.15126656,
        0.92649390,
        0.25494941,
        0.73275293,
        0.94184393,
        0.84755226,
        0.45921936,
        0.72934054,
        0.43722403,
        0.34305596,
        0.10827860,
        0.29026676,
        0.01935431,
        0.46668573,
        0.83247509,
        0.26349603,
        0.01938542,
        0.43222250,
        0.18109983,
        0.29337450,
        0.16721917,
        0.94751650,
        0.67795254,
        0.56666228,
        0.20699452,
        0.23247262,
        0.19138610,
        0.73495506,
        0.85893600,
        0.83411526,
        0.93689655,
        0.91804752,
        0.99352333,
        0.03207550,
        0.28386071,
        0.48029543,
        0.18736013,
        0.31736452,
        0.72542230,
        0.57530912,
        0.04229918,
        0.84798296,
        0.21886935,
        0.98655615,
        0.52243102,
        0.22611020,
        0.42975741,
        0.21726739,
        0.10912048,
        0.96684473,
        0.01092456,
        0.12461901,
        0.57989070,
        0.39848707,
        0.06330277,
        0.62826828,
        0.01159081,
        0.23157320,
        0.64690912,
        0.44876902,
        0.04463930,
        0.18933780,
        0.21284518,
        0.61363480,
        0.67144845,
        0.38625586,
        0.75719122,
        0.40361050,
        0.26708873,
        0.54534727,
        0.90174015,
        0.58654140,
        0.44885346,
        0.35505544,
        0.65317830,
        0.26074572,
        0.39472912,
        0.54366914,
        0.75020660,
        0.76113614,
        0.24595582,
        0.03941247,
        0.60356153,
        0.23615721,
        0.01603475,
        0.72432457,
        0.39837424,
        0.04195329,
        0.81561058,
        0.34208440,
        0.00513953,
        0.92826234,
        0.11410393,
        0.86692030,
        0.25238726,
        0.98258626,
        0.53353856,
        0.72269001,
        0.71850984,
        0.66829681,
        0.03540769,
        0.01676450,
        0.23557835,
        0.78758497,
        0.85969589,
        0.14673207,
        0.28013860,
        0.17796942,
        0.69924087,
        0.44663597,
        0.62112513,
        0.44079883,
        0.48995231,
        0.18411497,
        0.18440877,
        0.74016388,
        0.28845694,
        0.22969080,
        0.76851164,
        0.15551473,
        0.28980810,
        0.40906710,
        0.47619039,
        0.72611392,
        0.55802939,
        0.69365597,
        0.85736313,
        0.83343150,
        0.21324760,
        0.45327806,
        0.33053855,
        0.98198279,
        0.53279389,
        0.76877035,
        0.20548656,
        0.37065042,
        0.59026910,
        0.67418036,
        0.23585843,
        0.98156397,
        0.27849804,
        0.56198954,
        0.68752287,
        0.30073445,
        0.69348664,
        0.72515585,
        0.40629047,
        0.09320027,
        0.24334978,
        0.91407662,
        0.97226538,
        0.33904970,
        0.01717092,
        0.60155725,
        0.03001652,
        0.50979706,
        0.80531036,
        0.17450719,
        0.84984399,
        0.00498130,
        0.51636405,
        0.14080868,
        0.62289701,
        0.07853030,
        0.70567541,
        0.79844050,
        0.63766566,
        0.03559031,
        0.40994535,
        0.08423996,
        0.00389626,
        0.50608347,
        0.19622681,
        0.90537903,
        0.75458034,
        0.75102094,
        0.81491673,
        0.92925931,
        0.38074332,
        0.54817053,
        0.72593246,
        0.02146791,
        0.57990460,
        0.87921074,
        0.59913886,
        0.66726893,
        0.24269154,
        0.73344575,
        0.71826052,
        0.92313935,
        0.05212996,
        0.93771536,
        0.69489385,
        0.57581887,
        0.48106155,
        0.06808800,
        0.33633940,
        0.69142320,
        0.46566781,
        0.70654143,
        0.16541368,
        0.76257631,
        0.82777900,
        0.62958327,
        0.34757935,
        0.10891487,
        0.79912728,
        0.01156543,
        0.23111261,
        0.58535640,
        0.87461956,
        0.21723454,
        0.80409615,
        0.33169686,
        0.72800785,
        0.31218099,
        0.13729737,
        0.41637635,
        0.01234597,
        0.58313811,
        0.66746028,
        0.05105595,
        0.14930937,
        0.56044864,
        0.76196851,
        0.98800104,
        0.37075949,
        0.88740864,
        0.40697115,
        0.96598278,
        0.86013661,
        0.85386784,
        0.23986516,
        0.39027464,
        0.59593927,
        0.00161530,
        0.31768197,
        0.65702729,
        0.66461914,
        0.62937471,
        0.92120758,
        0.87578958,
        0.37539860,
        0.59182615,
        0.12092214,
        0.55130437,
        0.86365117,
        0.38725162,
        0.28757657,
        0.42803199,
        0.39014405,
        0.50253853,
        0.85306128,
        0.92018995,
        0.71421618,
        0.54236780,
        0.96221157,
        0.22956898,
        0.96519876,
        0.06694102,
        0.11915854,
        0.01354308,
        0.24720070,
        0.71671739,
        0.00604305,
        0.65012352,
        0.71151390,
        0.46616159,
        0.99228224,
        0.20684576,
        0.62941006,
        0.84535326,
        0.30678993,
        0.55264568,
        0.50094784,
        0.39409122,
        0.15479416,
        0.36536318,
        0.51925656,
        0.65567178,
        0.67255519,
        0.55089659,
        0.42194295,
        0.27172413,
        0.79540954,
        0.71594806,
        0.88372598,
        0.29179452,
        0.66411306,
        0.57064687,
        0.42494633,
        0.73389255,
        0.12097313,
        0.53338622,
        0.38493233,
        0.79348021,
        0.01851341,
        0.58594454,
        0.88396240,
        0.04410730,
        0.67419924,
        0.62770011,
        0.64644200,
        0.40335135,
        0.17952644,
        0.55564678,
        0.56643922,
        0.37715015,
        0.87092180,
        0.56726159,
        0.34011210,
        0.13661819,
        0.11474177,
        0.93930097,
        0.48549077,
        0.28484289,
        0.13374371,
        0.40966056,
        0.73662873,
        0.37355323,
        0.65216092,
        0.27372469,
        0.56032082,
        0.14882684,
        0.95462890,
        0.17090266,
        0.92374766,
        0.98368259,
        0.68448367,
        0.02872548,
        0.68598279,
        0.04601084,
        0.17170501,
        0.08906644,
        0.23730372,
        0.02929037,
        0.38566261,
        0.68957569,
        0.53021050,
        0.44200157,
        0.32085701,
        0.72520053,
        0.17454174,
        0.19676599,
        0.88243877,
        0.87030228,
        0.15124486,
        0.78670160,
        0.51731632,
        0.56674531,
        0.20910664,
        0.84962640,
        0.05220467,
        0.91783159,
        0.19138968,
        0.68126378,
        0.79574471,
        0.14910848,
        0.28030331,
        0.98067264,
        0.31263980,
        0.67448964,
        0.69266650,
        0.40033551,
        0.22789781,
        0.78317066,
        0.55815261,
        0.11247054,
        0.47337901,
        0.46310033,
        0.53192452,
        0.56164078,
        0.41750378,
        0.43880622,
        0.69739327,
        0.11092778,
        0.18333765,
        0.67222441,
        0.12789170,
        0.88316806,
        0.37891271,
        0.14935268,
        0.64522185,
        0.93902079,
        0.62481092,
        0.21794927,
        0.71535266,
        0.62169579,
        0.65147153,
        0.01411645,
        0.96413465,
        0.01021578,
        0.50605180,
        0.51595053,
        0.03308040,
        0.01497870,
        0.07809658,
        0.35743383,
        0.58079701,
        0.11785557,
        0.89568677,
        0.38793964,
        0.37117709,
        0.13994133,
        0.11032813,
        0.99998594,
        0.06695042,
        0.79774786,
        0.11093584,
        0.23879095,
        0.85918615,
        0.16109636,
        0.63479696,
        0.75023359,
        0.29061187,
        0.53764772,
        0.30652318,
        0.51387302,
        0.81620973,
        0.82433610,
        0.18302488,
        0.79048957,
        0.07598187,
        0.27887732,
        0.37061042,
        0.36441016,
        0.93736882,
        0.77480946,
        0.02269132,
        0.40309874,
        0.16427650,
        0.13969296,
        0.57605029,
        0.00242426,
        0.56626691,
        0.84390990,
        0.87455806,
        0.12321023,
        0.87561663,
        0.60431578,
        0.35880839,
        0.50426282,
        0.50697689,
        0.06631164,
        0.14976092,
        0.89356018,
        0.91473662,
        0.04235237,
        0.50073724,
        0.75969690,
        0.91743994,
        0.79352335,
        0.58078351,
        0.91819984,
        0.53520520,
        0.18267367,
        0.05608828,
        0.68315721,
        0.27264599,
        0.41245634,
        0.69706222,
        0.69666203,
        0.08967342,
        0.64081905,
        0.22576796,
        0.69315628,
        0.53981640,
        0.76059129,
        0.56712344,
        0.94318621,
        0.44081094,
        0.31699284,
        0.29477911,
        0.80069824,
        0.28366921,
        0.96718081,
        0.85345644,
        0.11681215,
        0.47600710,
        0.33448255,
        0.31217271,
        0.35469241,
        0.59511650,
        0.49583692,
        0.48922303,
        0.20215259,
        0.60159380,
        0.17882055,
        0.77601258,
        0.71020391,
        0.41833503,
        0.71522856,
        0.87534517,
        0.43703394,
        0.43056077,
        0.64828071,
        0.43069441,
        0.39356849,
        0.32063367,
        0.92788963,
        0.16878266,
        0.56762591,
        0.56042446,
        0.84958464,
        0.79408949,
        0.08220340,
        0.13922856,
        0.82529019,
        0.27134959,
        0.00278080,
        0.66192389,
        0.01782933,
        0.95404763,
        0.50787645,
        0.85320521,
        0.83690362,
        0.83771227,
        0.46268665,
        0.31716742,
        0.01716647,
        0.68264674,
        0.01789888,
        0.30446846,
        0.14942271,
        0.26982182,
        0.74933947,
        0.50394161,
        0.78444542,
        0.40009256,
        0.40333422,
        0.16627342,
        0.01898760,
        0.04221829,
        0.77960213,
        0.66230976,
        0.56015996,
        0.49535426,
        0.38536259,
        0.40406773,
        0.99930568,
        0.00857945,
        0.16158390,
        0.64805163,
        0.20237524,
        0.59106326,
        0.76968277,
        0.96887042,
        0.29264851,
        0.97373775,
        0.16767633,
        0.33014482,
        0.27426548,
        0.10947014,
        0.75920652,
        0.37757457,
        0.13125207,
        0.00826451,
        0.96684342,
        0.69362226,
        0.22763554,
        0.20717541,
        0.42112268,
        0.22803038,
        0.33481806,
        0.14968742,
        0.71598558,
        0.55126711,
        0.64518015,
        0.65170197,
        0.89103003,
        0.72728361,
        0.24485454,
        0.09410780,
        0.79818029,
        0.54212409,
        0.17790462,
        0.64442619,
        0.62193511,
        0.51193256,
        0.02848781,
        0.05719604,
        0.45795152,
        0.03219332,
        0.28310254,
        0.85746127,
        0.64890240,
        0.20658356,
        0.50946422,
        0.80432490,
        0.08354468,
        0.09222723,
        0.67455943,
        0.44638771,
        0.76366629,
        0.99677267,
        0.89311242,
        0.11627279,
        0.09181302,
        0.44767077,
        0.16448724,
        0.26005539,
        0.28670391,
        0.52465703,
        0.43598116,
        0.41869096,
        0.98043420,
        0.01497272,
        0.51791571,
        0.61825308,
        0.85503436,
        0.63025655,
        0.02719292,
        0.09865668,
        0.30321729,
        0.56998039,
        0.14946350,
        0.64823918,
        0.19931639,
        0.14623555,
        0.54169913,
        0.68944135,
        0.73551005,
        0.46743658,
        0.04109096,
        0.26625801,
        0.09537298,
        0.98207890,
        0.58109721,
        0.70793680,
        0.84379365,
        0.42774726,
        0.12653597,
        0.08566633,
        0.53366781,
        0.33960092,
        0.11036831,
        0.84464510,
        0.16493476,
        0.92493443,
        0.87640673,
        0.52727644,
        0.57181349,
        0.65071340,
        0.00978637,
        0.31700693,
        0.69148222,
        0.85063311,
        0.06781819,
        0.30794534,
        0.65541667,
        0.16400484,
        0.06886223,
        0.96227205,
        0.09633060,
        0.34513153,
        0.31013900,
        0.78165882,
        0.39583699,
        0.86327936,
        0.69269199,
        0.11016575,
        0.67358419,
        0.81775427,
        0.50052824,
        0.30068582,
        0.16606837,
        0.62243724,
        0.47863741,
        0.68796498,
        0.31526949,
        0.41180883,
        0.23022147,
        0.82342139,
        0.83003381,
        0.53571829,
        0.41081533,
        0.48600142,
    ]
    println("\n1000 outputs of Float64")
    for i = 1:1000
        z = rand(gen, Float64)
        # print(" " ,z)
        # if (i % 5 == 4)
        #     println()
        # end
        @test isapprox(referenceNumbers[i], z; atol = 1e-7)
    end
end


module LoopVectorization

using VectorizationBase, SIMDPirates, SLEEFPirates, MacroTools, Parameters
using VectorizationBase: REGISTER_SIZE, REGISTER_COUNT, extract_data, num_vector_load_expr,
    mask, masktable, pick_vector_width_val, valmul, valrem, valmuladd, valadd, valsub
using SIMDPirates: VECTOR_SYMBOLS, evadd, evmul, vrange, reduced_add, reduced_prod
using Base.Broadcast: Broadcasted, DefaultArrayStyle
using LinearAlgebra: Adjoint, Transpose
using MacroTools: prewalk, postwalk

export LowDimArray, stridedpointer, vectorizable,
    @avx, *ˡ, ∗,
    vmap, vmap!

include("costs.jl")
include("operations.jl")
include("graphs.jl")
include("broadcast.jl")
include("determinestrategy.jl")
include("lowering.jl")
include("constructors.jl")
include("map.jl")
# include("precompile.jl")
# _precompile_()

end # module


using Mill, Test, Flux
using Mill: partialeval, identity_model

@testset "partialeval" begin
    metadata = fill("metadata", 4)
    an1 = ArrayNode(rand(3,4))
    b = BagNode(an1, [1:4, 0:-1], metadata)
    an2 = ArrayNode(randn(5, 4))
    wb = WeightedBagNode(an2, [1:2,3:4], rand(1:4, 4), metadata)
    pn = ProductNode((b=b,wb=wb))
    an3 = ArrayNode(rand(10, 2))
    ds = ProductNode((pn, an3))
    # printtree(ds)
    m = reflectinmodel(ds, d -> Chain(Dense(d, 4, relu), Dense(4,3)), d-> SegmentedMeanMax(d))

    @test partialeval(m.ms[2], ds.data[2], an3)[1] === m.ms[2]
    @test partialeval(m.ms[2], ds.data[2], an3)[2] === ds.data[2]
    @test partialeval(m.ms[2], ds.data[2], an1)[1] === identity_model()
    @test partialeval(m.ms[2], ds.data[2], an1)[2].data ≈ m.ms[2](ds.data[2]).data
    tm, td = partialeval(m.ms[2], ds.data[2], an3)
    @test tm(td).data ≈ m.ms[2](ds.data[2]).data
    @test partialeval(m.ms[2], ds.data[2], an1)[2].data ≈ m.ms[2](ds.data[2]).data

    @test partialeval(m.ms[1][:b], ds.data[1][:b], an1)[1] === m.ms[1][:b]
    @test partialeval(m.ms[1][:b], ds.data[1][:b], an1)[2] === ds.data[1][:b]
    @test partialeval(m.ms[1][:b], ds.data[1][:b], b)[1] === m.ms[1][:b]
    @test partialeval(m.ms[1][:b], ds.data[1][:b], b)[2] === ds.data[1][:b]
    tm, td = partialeval(m.ms[1][:b], ds.data[1][:b], an3)
    @test tm(td).data ≈ m.ms[1][:b](ds.data[1][:b]).data
    @test partialeval(m.ms[1][:b], ds.data[1][:b], an3)[1] === identity_model()
    @test partialeval(m.ms[1][:b], ds.data[1][:b], an3)[2].data ≈ m.ms[1][:b](ds.data[1][:b]).data

    @test partialeval(m.ms[1][:wb], ds.data[1][:wb], an2)[1] === m.ms[1][:wb]
    @test partialeval(m.ms[1][:wb], ds.data[1][:wb], an2)[2] === ds.data[1][:wb]
    @test partialeval(m.ms[1][:wb], ds.data[1][:wb], wb)[1] === m.ms[1][:wb]
    @test partialeval(m.ms[1][:wb], ds.data[1][:wb], wb)[2] === ds.data[1][:wb]
    tm, td = partialeval(m.ms[1][:wb], ds.data[1][:wb], an3)
    @test tm(td).data ≈ m.ms[1][:wb](ds.data[1][:wb]).data
    @test partialeval(m.ms[1][:wb], ds.data[1][:wb], an3)[1] === identity_model()
    @test partialeval(m.ms[1][:wb], ds.data[1][:wb], an3)[2].data ≈ m.ms[1][:wb](ds.data[1][:wb]).data

    @test partialeval(m.ms[1], ds.data[1], an1)[1].ms[1] === m.ms[1].ms[1]
    @test partialeval(m.ms[1], ds.data[1], an1)[2].data[1] === ds.data[1].data[1]
    @test partialeval(m.ms[1], ds.data[1], an1)[1].ms[2] === identity_model()
    @test partialeval(m.ms[1], ds.data[1], an1)[2].data[2].data ≈ m.ms[1].ms[2](ds.data[1].data[2]).data
    @test partialeval(m.ms[1], ds.data[1], an3)[1] === identity_model()
    @test partialeval(m.ms[1], ds.data[1], an3)[2].data ≈ m.ms[1](ds.data[1]).data
    tm, td = partialeval(m.ms[1], ds.data[1], an1)
    @test tm(td).data ≈ m.ms[1](ds.data[1]).data
    @test partialeval(m.ms[2], ds.data[2], an1)[2].data ≈ m.ms[2](ds.data[2]).data

    @test partialeval(m, ds, 1)[1] === identity_model()
    @test partialeval(m, ds, 1)[2].data ≈ m(ds).data
    tm, td = partialeval(m, ds, an1)
    @test tm(td).data ≈ m(ds).data
end


mutable struct GenericModel{T} <: AbstractModel{T}
    parameters::ParameterVector{T}
    steady_state::ParameterVector{T}
    keys::Dict{Symbol, Int}
    endogenous_states::Dict{Symbol,Int}             # these fields used to create matrices in the
    exogenous_shocks::Dict{Symbol,Int}              # measurement and equilibrium condition equations.
    observables::Dict{Symbol,Int}                   #
    pseudo_observables::Dict{Symbol,Int}            #
    spec::String
    subspec::String
    settings::Dict{Symbol, Setting}
    test_settings::Dict{Symbol, Setting}
    rng::MersenneTwister
    testing::Bool
    observable_mappings::Dict{Symbol, Observable}
    pseudo_observable_mappings::Dict{Symbol, PseudoObservable}
end

function GenericModel(spec::String = "", subspec::String = ""; custom_settings::Dict{Symbol, Setting} = Dict{Symbol, Setting}(), testing = false)
    settings = Dict{Symbol, Setting}()
    test_settings = Dict{Symbol, Setting}()
    rng = MersenneTwister(0)

    m = GenericModel{Float64}(
        Vector{AbstractParameter{Float64}}(), Vector{AbstractParameter{Float64}}(),
        Dict{Symbol, Int}(),
        Dict{Symbol, Int}(), Dict{Symbol, Int}(), Dict{Symbol, Int}(), Dict{Symbol, Int}(),
        spec, subspec, settings, test_settings, rng, testing,
        Dict{Symbol, Observable}(), Dict{Symbol, PseudoObservable}())

    model_settings!(m)
    default_test_settings!(m)
    for custom_setting in values(custom_settings)
        m <= custom_setting
    end

    return m
end

description(m::GenericModel) = "A generic model: You can put parameters, states, shocks, observables, pseudo-observables, spec, subpsec, settings inside of me."

function model_settings!(m::GenericModel)

end


mutable struct MapAssessState{T}
    weight::Float64
    retvals::Vector{T}
end

function process_new!(gen_fn::Map{T,U}, args::Tuple, choices::ChoiceMap,
                      key::Int, state::MapAssessState{T}) where {T,U}
    kernel_args = get_args_for_key(args, key)
    submap = get_submap(choices, key)
    (weight, retval) = assess(gen_fn.kernel, kernel_args, submap)
    state.weight += weight
    state.retvals[key] = retval
end

function assess(gen_fn::Map{T,U}, args::Tuple, choices::ChoiceMap) where {T,U}
    len = length(args[1])
    state = MapAssessState{T}(0., Vector{T}(undef,len))
    for key=1:len
        process_new!(gen_fn, args, choices, key, state)
    end
    (state.weight, PersistentVector{T}(state.retvals))
end


@testset "@q macro" begin
    @test 4 == @q 2 + 2
    @test [0, 1] == @q til(2)
    @test begin
        @q begin
          a = 2
          b = 42
        end
        @q(a) == 2 && @q(b) == 42
    end
    @test begin
        f = Dict(:x=>42)
        e = K(:(x+y))
        Q.resolve!(e, f)
        e == K(:(42+y))
    end
    @test (x = 42; 43 == @q let x;x+1 end)
    @test q(".z.K") == @q _z.K
end


using Pio3d

function prepare_ircad(id=1)
    # fn = io3d.datasets.join_path("medical", "orig", "3Dircadb1.1", "MASKS_DICOM", "liver", get_root=True)
    pth = Pio3d.datasets_join_path("medical/orig/3Dircadb1.$id/MASKS_DICOM/liver")

    datap = Pio3d.read3d(pth)
    data3d_full = datap["data3d"]
    # round(size(data3d_full, 1) / target_size1)
    # return data3d_full
    data3d_int8 = convert(Array{Int8, 3}, data3d_full .> 0)
    @info "reading Ircad $id done"

    return data3d_int8
end

# im = prepare_ircad(2)


using CategoricalArrays
import Statistics

"""
    reorder!(fac::::CategoricalVector, v::Vector{<:Real}, f::Function=Statistics.mean)

Assign the levels of the ordered categorical vector `fac` in increasing order of `f(v)`
applied to what would be written in R as `split(v)`.
"""
function reorder!(fac::CategoricalVector, v::Vector{<:Real}, f::Function=Statistics.mean)
    if length(fac) ≠ length(v)
        throw(DimensionMismatch("lengths of fac and v must be equal"))
    end
    dict = Dict(l => eltype(v)[] for l in levels(fac))
    for (l, vv) in zip(fac, v)
        push!(dict[l], vv)
    end
    levels!(fac, collect(keys(sort(Dict(k => f(v) for (k,v) in dict), byvalue=true))))
end


using HTTP, JSON

const HOST = "127.0.0.1"
const PORT = 8080
const ROUTER = HTTP.Router()

function show_factorial(req::HTTP.Request)
  headers = [                                       # 7
    "Access-Control-Allow-Origin" => "*",
    "Access-Control-Allow-Methods" => "POST, OPTIONS"
  ]
  if HTTP.method(req) == "OPTIONS"                  # 8
    return HTTP.Response(200, headers)
  end
  body = parse(Int64, String(HTTP.body(req)))       # 4
  fact = factorial(big(body))                       # 5
  HTTP.Response(200, headers; body = string(fact))  # 6
end

function render(req::HTTP.Request)
    HTTP.Response(200, read(raw"index.html"))
end

HTTP.@register(ROUTER, "POST", "/factorial", show_factorial)  # 3
HTTP.@register(ROUTER, "GET", "/", render)        # 2

HTTP.serve(ROUTER, HOST, PORT)                    # 1


using BDF, Base.Test

origFilePath = joinpath(dirname(@__FILE__), "Newtest17-256.bdf")

dats, evtTab, trigs, statusChan = readBDF(origFilePath)

for ci in ([1, 2, 3], [1, 2, 3, 4, 5, 6], 1:14, [1, 3, 9, 11, 15], [5, 2, 7])

    dats2, evtTab2, trigs2, statusChan2 = readBDF(origFilePath, channels = ci)

    @test size(dats2, 2) == size(dats, 2)
    @test size(dats2, 1) == length(ci)

    @test_approx_eq(dats[ci, :], dats2)
    @test statusChan2 == statusChan

end

for (ci, el) in zip((["A1", "A2"], ["A1", "A3"], ["A3", "A1", "A11", "A16"]), ([1, 2], [1, 3], [3, 1, 11, 16]))

    dats2, evtTab2, trigs2, statusChan2 = readBDF(origFilePath, channels = ci)

    @test size(dats2, 2) == size(dats, 2)
    @test size(dats2, 1) == length(ci)

    @test_approx_eq(dats[el, :], dats2)
    @test statusChan2 == statusChan
end


## dg.jl

@testset "test simple_allowed_reactions allow_missing" begin

    @testset "some dgs" begin
        dgs = [101.1,15,-15,missing]
        threshold = -10.2

        @test [false,false,true,true]  == BioXP.simple_allowed_reactions(dgs,threshold,true)
        @test [false,false,true,false] == BioXP.simple_allowed_reactions(dgs,threshold,false)
    end

    @testset "value = threshold" begin
        dgs = [missing,missing,0,8,900]
        threshold = 8

        @test [true,true,true,true,false]   == BioXP.simple_allowed_reactions(dgs,threshold,true)
        @test [false,false,true,true,false] == BioXP.simple_allowed_reactions(dgs,threshold,false)
    end

end

@testset "test filter_reactions_by_dg" begin

    threshold = -75
    env_key = "11pH_300mM"
    rids = readids(["R02745","R05080","R00778","R08348","R06706"])
    rstructs = readmaster("data/master_with_dgs_reserialized.json")

    @testset "unbalanced only" begin
        allow_nothings=false
        allow_unbalanced=true
        allow_within_ci=false

        forward_allowed=[false,false,true,true,false]
        backward_allowed=[false,false,false,false,true]
        @test (forward_allowed, backward_allowed) == filter_reactions_by_dg(
                                                    threshold,
                                                    env_key,
                                                    rids,
                                                    rstructs,
                                                    allow_nothings=allow_nothings,
                                                    allow_unbalanced=allow_unbalanced,
                                                    allow_within_ci=allow_within_ci)

    end

    @testset "nothings and unbalanced" begin
        allow_nothings=true
        allow_unbalanced=true
        allow_within_ci=false

        forward_allowed=[true,true,true,true,false]
        backward_allowed=[true,true,false,false,true]
        @test (forward_allowed, backward_allowed) == filter_reactions_by_dg(
                                                    threshold,
                                                    env_key,
                                                    rids,
                                                    rstructs,
                                                    allow_nothings=allow_nothings,
                                                    allow_unbalanced=allow_unbalanced,
                                                    allow_within_ci=allow_within_ci)
    end

    @testset "nothings only" begin
        allow_nothings=true
        allow_unbalanced=false
        allow_within_ci=false

        forward_allowed=[false,false,true,true,false]
        backward_allowed=[false,false,false,false,false]
        @test (forward_allowed, backward_allowed) == filter_reactions_by_dg(
                                                    threshold,
                                                    env_key,
                                                    rids,
                                                    rstructs,
                                                    allow_nothings=allow_nothings,
                                                    allow_unbalanced=allow_unbalanced,
                                                    allow_within_ci=allow_within_ci)
    end

    @testset "ci only" begin
        allow_nothings=false
        allow_unbalanced=false
        allow_within_ci=true

        forward_allowed=[false,false,true,true,false]
        backward_allowed=[false,false,true,false,false]
        @test (forward_allowed, backward_allowed) == filter_reactions_by_dg(
                                                    threshold,
                                                    env_key,
                                                    rids,
                                                    rstructs,
                                                    allow_nothings=allow_nothings,
                                                    allow_unbalanced=allow_unbalanced,
                                                    allow_within_ci=allow_within_ci)

    end

    @testset "nothings and ci" begin #this doesn't change anything either
        allow_nothings=true
        allow_unbalanced=false
        allow_within_ci=true

        forward_allowed=[false,false,true,true,false]
        backward_allowed=[false,false,true,false,false]
        @test (forward_allowed, backward_allowed) == filter_reactions_by_dg(
                                                    threshold,
                                                    env_key,
                                                    rids,
                                                    rstructs,
                                                    allow_nothings=allow_nothings,
                                                    allow_unbalanced=allow_unbalanced,
                                                    allow_within_ci=allow_within_ci)
    end

    @testset "unbalanced and ci" begin #ci doesn't open any new reactions here
        allow_nothings=false
        allow_unbalanced=true
        allow_within_ci=true

        forward_allowed=[false,false,true,true,false]
        backward_allowed=[false,false,true,false,true]
        @test (forward_allowed, backward_allowed) == filter_reactions_by_dg(
                                                    threshold,
                                                    env_key,
                                                    rids,
                                                    rstructs,
                                                    allow_nothings=allow_nothings,
                                                    allow_unbalanced=allow_unbalanced,
                                                    allow_within_ci=allow_within_ci)
    end

    @testset "all allowed" begin #ci doesn't open any new reactions here
        allow_nothings=true
        allow_unbalanced=true
        allow_within_ci=true

        forward_allowed=[true,true,true,true,false]
        backward_allowed=[true,true,true,false,true]
        @test (forward_allowed, backward_allowed) == filter_reactions_by_dg(
                                                    threshold,
                                                    env_key,
                                                    rids,
                                                    rstructs,
                                                    allow_nothings=allow_nothings,
                                                    allow_unbalanced=allow_unbalanced,
                                                    allow_within_ci=allow_within_ci)
    end

    ## need to test where ci allows new reaction--change threshold

end


"""
    DFTU(;l ::Int = -1,
          U ::T   = zero(T),
          J0::T  = zero(T),
          α ::T   = zero(T),
          β ::T   = zero(T),
          J ::Vector{T} = T[zero(T)])

DFT+U parameters for a given [`Atom`](@ref).
"""
@with_kw mutable struct DFTU
    l::Int      = -1
    U::Float64  = 0.0
    J0::Float64 = 0.0
    #QE params
    α::Float64 = 0.0
    β::Float64 = 0.0
    J::Vector{Float64} = [0.0]
end

function Base.:(==)(x::DFTU, y::DFTU)
    fnames = fieldnames(DFTU)
    for fn in fnames
        if getfield(x, fn) != getfield(y, fn)
            return false
        end
    end
    return true
end

StructTypes.StructType(::Type{DFTU}) = StructTypes.Mutable()

"""
    Element(symbol::Symbol, Z::Int, name::String, atomic_weight::Float64, color::NTuple{3, Float64})

Represents an element. Most conveniently used trough the function [`element`](@ref).
"""
struct Element
    symbol        :: Symbol
    Z             :: Int64
    name          :: String
    atomic_weight :: Float64
    color         :: NTuple{3,Float64}
end
Element() = Element(:nothing, -1, "", -1, (0.0, 0.0, 0.0))
StructTypes.StructType(::Type{Element}) = StructTypes.Struct()

"""
Reads all the elements from the file.
"""
const ELEMENTS = Element[]

open(joinpath(@__DIR__, "..", "..", "assets", "elements.txt"), "r") do f
    while !eof(f)
        line = split(readline(f))
        push!(ELEMENTS,
              Element(Symbol(line[4]), Meta.parse(line[1]), line[9], Meta.parse(line[10]),
                      (Meta.parse.(line[5:7],)...,) ./ 65535))
    end
end
#undefined element
push!(ELEMENTS, Element(:undef, 0, "undef", 0, (0.0, 0.0, 0.0)))

"""
    element(sym::Symbol)

Returns the predefined [`Element`](@ref) with symbol `sym`,
i.e. `element(:Si)` will return the pregenerated Silicon [`Element`](@ref).
"""
function element(sym::Symbol)
    # This we do to clean up atom names such as :Fe1
    if tryparse(Int, String(sym)[end:end]) !== nothing
        sym = Symbol(String(sym)[1:end-1])
    end
    found = filter(x -> x.symbol == sym, ELEMENTS)
    if isempty(found)
        return ELEMENTS[end]
    end
    return found[1]
end

element(z::Int) = getfirst(x -> x.Z == z, ELEMENTS)

# TODO Multiple l per atom in Elk??
#We use angstrom everywhere
"""
    Atom(name::Symbol, element::Element, position_cart::Point3{Length}, position_cryst::Point3;
         pseudo::String = "",
         projections::Vector{Projection} = Projection[],
         magnetization::Vec3 = Vec3(0.0, 0.0, 0.0),
         dftu::DFTU = DFTU())

Representation of an `atom`.

The `name` of the `atom` is used as an identifier for the `atom` type, in the sense that atoms with the same `pseudo`, `projections`, `magnetization` and [`dftu`](@ref DFTU) attributes should belong to the same type. This also means that during sanity checks atoms that are not of the same type will be given different names. This is done in this way because it often makes sense to change these parameters on all atoms of the same kind at the same time, but still allow the flexibility to change them for individual atoms as well.

`position_cart` should have a valid `Unitful.Length` type such as `Ang`.

See documentation for [`Element`](@ref) for further information on this attribute.
"""
@with_kw_noshow mutable struct Atom
    name::Symbol
    position_cart::Point3{typeof(0.0Ang)}
    position_cryst::Point3{Float64}
    element::Element = element(name)
    pseudo::String = ""
    projections::Vector{Projection} = Projection[]
    magnetization::Vec3{Float64} = zero(Vec3{Float64})
    dftu::DFTU = DFTU()
end
StructTypes.StructType(::Type{Atom}) = StructTypes.Struct()

"Takes a Vector of atoms and returns a Vector with the atoms having unique symbols."
function Base.unique(atoms::Vector{Atom})
    uni = Atom[]
    for at in atoms
        if findfirst(x -> isequal_species(x, at), uni) === nothing
            push!(uni, at)
        end
    end
    return uni
end

function isequal_species(at1::Atom, at2::Atom)
    for f in fieldnames(typeof(at1))
        if f in (:position_cart, :position_cryst, :projections)
            continue
        end
        if getfield(at1, f) != getfield(at2, f)
            return false
        end
    end
    return true
end

function Base.range(at::Atom)
    projs = at.projections
    @assert length(projs) != 0 "At $(at.name) has no defined projections. Please use `setprojections!` first."
    return projs[1].start:projs[end].last
end

Base.range(v::Vector{Atom}) = vcat(range.(v)...)

function projections_string(at::Atom)
    return "$(at.name): " * join(map(x -> x.orbital.name, at.projections), ";")
end

function Base.:(==)(at1::Atom, at2::Atom)
    return at1.name == at2.name && norm(at1.position_cart - at2.position_cart) < 1e-6Ang
end

"""
	set_position!(at::Atom, pos::AbstractVector{T}, unit_cell::Mat3) where {T<:Real}

Updates the position of the atom to this. The unit cell is used to make sure both `position_cryst` and `position_cart` are correct.
"""
function set_position!(at::Atom, pos::AbstractVector{T}, unit_cell::Mat3) where {T<:Real}
    at.position_cryst = Point3{T}(pos...)
    at.position_cart  = unit_cell * at.position_cryst
    return at
end

function set_position!(at::Atom, pos::AbstractVector{T}, unit_cell::Mat3) where {T<:Length}
    at.position_cart  = Point3{T}(pos...)
    at.position_cryst = unit_cell^-1 * at.position_cart
    return at
end

"""
	scale_bondlength!(at1::Atom, at2::Atom, scale::Real, cell::Mat3)

Scales the bondlength between two atoms. The center of mass remains the same.
"""
function scale_bondlength!(at1::Atom, at2::Atom, scale::Real, cell::Mat3)
    p1 = at.position_cryst
    p2 = at.position_cryst
    mid = (p1 + p2) / 2
    orig_dist = norm(p1 - p2)
    direction = (p1 - mid) / norm(p1 - mid)
    new_dist = orig_dist * scale
    new_p1 = mid + new_dist * direction / 2
    new_p2 = mid - new_dist * direction / 2
    set_position!(at1, new_p1, cell)
    return set_position!(at2, new_p2, cell)
end


function complementary(v::AbstractArray{Float64}, n::Int64)
  ns = collect(1:n)
  setdiff(1:n, v)
end

function complementary(v::Vector{Float64}, n::Int64)
  ns = collect(1:n)
  setdiff(1:n, v)
end

function complementary(v::Vector{Int}, n::Int64)
  ns = collect(1:n)
  setdiff(1:n, v)
end

function complementary(v::Int64, n::Int64)
  ns = collect(1:n)
  setdiff(1:n, v)
end

function complementary(v::AbstractArray{Int64}, n::Int64)
  ns = collect(1:n)
  setdiff(1:n, v)
end


# https://github.com/nolta/Winston.jl

# credit goes to https://github.com/jverzani for contributing to the first draft of this backend implementation

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

## dictionaries for conversion of Plots.jl names to Winston ones.
@compat const winston_linestyle =  Dict(:solid=>"solid",
                                :dash=>"dash",
                                :dot=>"dotted",
                                :dashdot=>"dotdashed"
                               )

@compat const winston_marker = Dict(:none=>".",
                            :rect => "square",
                            :ellipse=>"circle",
                            :diamond=>"diamond",
                            :utriangle=>"triangle",
                            :dtriangle=>"down-triangle",
                            :cross => "plus",
                            :xcross => "cross",
                            :star5 => "asterisk"
                           )

function _before_add_series(plt::Plot{WinstonPackage})
  Winston.ghf(plt.o)
end

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

function _create_plot(pkg::WinstonPackage; kw...)
  d = Dict(kw)
  wplt = Winston.FramedPlot(title = d[:title], xlabel = d[:xlabel], ylabel = d[:ylabel])

  Plot(wplt, pkg, 0, d, Dict[])
end

copy_remove(d::Dict, s::Symbol) = delete!(copy(d), s)

function addRegressionLineWinston(d::Dict, wplt)
  xs, ys = regressionXY(d[:x], d[:y])
  Winston.add(wplt, Winston.Curve(xs, ys, kind="dotted"))
end

function getWinstonItems(plt::Plot)
  if isa(plt.o, Winston.FramedPlot)
    wplt = plt.o
    window, canvas = nothing, nothing
  else
    window, canvas, wplt = plt.o
  end
  window, canvas, wplt
end

function _add_series(::WinstonPackage, plt::Plot; kw...)
  d = Dict(kw)

  window, canvas, wplt = getWinstonItems(plt)

  # until we call it normally, do the hack
  if d[:linetype] == :bar
    d = barHack(;d...)
  end

  e = Dict()
  e[:color] = getColor(d[:linecolor])
  e[:linewidth] = d[:linewidth]
  e[:kind] = winston_linestyle[d[:linestyle]]
  e[:symbolkind] = winston_marker[d[:markershape]]
  # markercolor     # same choices as `color`, or :match will set the color to be the same as `color`
  e[:symbolsize] = d[:markersize] / 5

  # pos             # (Int,Int), move the enclosing window to this position
  # screen          # Integer, move enclosing window to this screen number (for multiscreen desktops)

  ## lintype :path, :step, :stepinverted, :sticks, :dots, :none, :heatmap, :hexbin, :hist, :bar
  if d[:linetype] == :none
    Winston.add(wplt, Winston.Points(d[:x], d[:y]; copy_remove(e, :kind)..., color=getColor(d[:markercolor])))

  elseif d[:linetype] == :path
    x, y = d[:x], d[:y]
    Winston.add(wplt, Winston.Curve(x, y; e...))

    fillrange = d[:fillrange]
    if fillrange != nothing
      if isa(fillrange, AbstractVector)
        y2 = fillrange
      else
        y2 = Float64[fillrange for yi in y]
      end
      Winston.add(wplt, Winston.FillBetween(x, y, x, y2, fillcolor=getColor(d[:fillcolor])))
    end

  elseif d[:linetype] == :scatter
    if d[:markershape] == :none
      d[:markershape] = :ellipse
    end

  # elseif d[:linetype] == :step
  #     fn = Winston.XXX

  # elseif d[:linetype] == :stepinverted
  #     fn = Winston.XXX

  elseif d[:linetype] == :sticks
      Winston.add(wplt, Winston.Stems(d[:x], d[:y]; e...))

  # elseif d[:linetype] == :dots
  #     fn = Winston.XXX

  # elseif d[:linetype] == :heatmap
  #     fn = Winston.XXX

  # elseif d[:linetype] == :hexbin
  #     fn = Winston.XXX

  elseif d[:linetype] == :hist
      hst = hist(d[:y], d[:nbins])
      Winston.add(wplt, Winston.Histogram(hst...; copy_remove(e, :nbins)...))

  # elseif d[:linetype] == :bar
  #     # fn = Winston.XXX

  else
    error("linetype $(d[:linetype]) not supported by Winston.")

  end

  # markershape
  if d[:markershape] != :none
    Winston.add(wplt, Winston.Points(d[:x], d[:y]; copy_remove(e, :kind)..., color=getColor(d[:markercolor])))
  end

  # optionally add a regression line
  d[:smooth] && d[:linetype] != :hist && addRegressionLineWinston(d, wplt)

  push!(plt.seriesargs, d)
  plt
end

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

@compat const _winstonNames = Dict(
    :xlims => :xrange,
    :ylims => :yrange,
    :xscale => :xlog,
    :yscale => :ylog,
  )

function _update_plot(plt::Plot{WinstonPackage}, d::Dict)
  window, canvas, wplt = getWinstonItems(plt)
  for k in (:xlabel, :ylabel, :title, :xlims, :ylims)
    if haskey(d, k)
      Winston.setattr(wplt, string(get(_winstonNames, k, k)), d[k])
    end
  end

  for k in (:xscale, :yscale)
    if haskey(d, k)
      islogscale = d[k] == :log10
      Winston.setattr(wplt, (k == :xscale ? :xlog : :ylog), islogscale)
    end
  end

end

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

function createWinstonAnnotationObject(plt::Plot{WinstonPackage}, x, y, val::@compat(AbstractString))
  Winston.text(x, y, val)
end

function _add_annotations{X,Y,V}(plt::Plot{WinstonPackage}, anns::AVec{@compat(Tuple{X,Y,V})})
  for ann in anns
    createWinstonAnnotationObject(plt, ann...)
  end
end

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

function _create_subplot(subplt::Subplot{WinstonPackage}, isbefore::Bool)
  # TODO: build the underlying Subplot object.  this is where you might layout the panes within a GUI window, for example
end

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

function addWinstonLegend(plt::Plot, wplt)
  if plt.plotargs[:legend]
    Winston.legend(wplt, [sd[:label] for sd in plt.seriesargs])
  end
end

function Base.writemime(io::IO, ::MIME"image/png", plt::PlottingObject{WinstonPackage})
  window, canvas, wplt = getWinstonItems(plt)
  addWinstonLegend(plt, wplt)
  writemime(io, "image/png", wplt)
end

function Base.display(::PlotsDisplay, plt::Plot{WinstonPackage})

  window, canvas, wplt = getWinstonItems(plt)

  if window == nothing
    if Winston.output_surface != :gtk
      error("Gtk is the only supported display for Winston in Plots.  Set `output_surface = gtk` in src/Winston.ini")
    end
    # initialize window
    w,h = plt.plotargs[:size]
    canvas = Gtk.GtkCanvasLeaf()
    window = Gtk.GtkWindowLeaf(canvas, plt.plotargs[:windowtitle], w, h)
    plt.o = (window, canvas, wplt)
  end

  addWinstonLegend(plt, wplt)

  Winston.display(canvas, wplt)
  Gtk.showall(window)
end

function Base.display(::PlotsDisplay, subplt::Subplot{WinstonPackage})
  # TODO: display/show the Subplot object
end


# Ball Tree

NO_CHILD = -1 # Maybe do int64(uint32(-1)) ??
FIELD_NAMES = ["D", "N", "centers", "ranges", "weights",
            "lower", "upper", "leftch", "rightch", "perm"]
nfields = 10;

type BallTree
  dims::Int64                     # dimension of data
  num_points::Int64               # of points
  centers::Array{Float64,1}       # ball centers, dims numbers per ball
  ranges::Array{Float64,1}        # bounding box ranges, dims per ball, dist from center to one side
  weights::Array{Float64,1}       # total weight in each ball

  left_child::Array{Int64,1}
  right_child::Array{Int64,1}     # left, right children; no parent indices
  lowest_leaf::Array{Int64,1}
  highest_leaf::Array{Int64,1}    # lower & upper leaf indices for each ball
  permutation::Array{Int64,1}     # point's position in the original data

  next::Int64                     # internal var for placing the non-leaf nodes

  swapHandle::Function
  calcStatsHandle::Function
  data
end

root() = 1
Ndim(bt::BallTree) = bt.dims
Npts(bt::BallTree) = bt.num_points
Npts(bt::BallTree, i::Int64) = bt.highest_leaf[i]-bt.lowest_leaf[i]+1

## todo make sure these two are working properly -- this is the problem, we are always assigning new memory for each return
#function center(bt::BallTree, i::Int64)
#    return bt.centers[((i-1)*bt.dims+1):end]
#end
center(bt::BallTree, i::Int64) = bt.centers[((i-1)*bt.dims+1):end]
center(bt::BallTree, i::Int64, k::Int) = bt.centers[((i-1)*bt.dims+k)]

#function rangeB(bt::BallTree, i::Int64)
#    return bt.ranges[((i-1)*bt.dims+1):end]
#end
rangeB(bt::BallTree, i::Int64) = bt.ranges[((i-1)*bt.dims+1):end]
rangeB(bt::BallTree, i::Int64, k::Int) = bt.ranges[((i-1)*bt.dims+k)]

#function weight(bt::BallTree, i::Int64)
#    return bt.weights[i]
#end
weight(bt::BallTree, i::Int64) = bt.weights[i]

#function isLeaf(bt::BallTree, ind::Int64)
#    return ind >= bt.num_points
#end
isLeaf(bt::BallTree, ind::Int64) = ind >= bt.num_points

validIndex(bt::BallTree, ind::Int64) = ((0<ind) && (ind <= 2*bt.num_points))

left(bt::BallTree, i::Int64) = bt.left_child[i]

right(bt::BallTree, i::Int64) = bt.right_child[i]

leafFirst(bt::BallTree, i::Int64) = bt.lowest_leaf[i]

leafLast(bt::BallTree, i::Int64) = bt.highest_leaf[i]

# Convert a BallTree::index to the numeric index in the original data
getIndexOf(bt::BallTree, i::Int64) = bt.permutation[i]

function swap!(data, _i::Int64, _j::Int64)
  return data.swapHandle(data, _i, _j)
end
function calcStats!(data, root::Int64)
  #@show "Fancy calcStats"
  return data.calcStatsHandle(data, root)
end

# Swap the ith leaf with the jth leaf.  Actually, only swap the
# weights, permutation, and centers, so only for swapping
# leaves. Will not swap ranges correctly and will not swap children
# correctly.
function swapBall!(bt::BallTree, _i::Int64, _j::Int64)
  #println("swapBall! -- (i,j)=$((_i,_j))")
  i = _i
  j = _j
  tmp = 0.
  if (i==j)
    return Union{}
  end

  # swap weights
  tmp = bt.weights[i]
  bt.weights[i] = bt.weights[j]
  bt.weights[j] = tmp

  # swap perm
  k = bt.permutation[i]
  bt.permutation[i] = bt.permutation[j]
  bt.permutation[j] = k;

  # swap centers
  i = (i-1)*bt.dims
  j = (j-1)*bt.dims;
  for k in 1:bt.dims
    i+=1
    j+=1
    tmp = bt.centers[i];
    bt.centers[i]  = bt.centers[j];
    bt.centers[j] = tmp;
  end
end

# Find the dimension along which the leaves between low and high
# inclusive have the greatest variance
function  most_spread_coord(bt::BallTree, low::Int64, high::Int64)
  #BallTree::index dimension, point, max_dim;
  #double mean, variance, max_variance;
  #println("most_spread_coord -- low, high = $((low, high))")
  max_variance = 0
  max_dim = 1

  for dimension = 1:bt.dims
    mean = 0
    for point = (bt.dims*(low-1) + dimension):bt.dims:(bt.dims*(high-1))
      mean += bt.centers[point]
    end
    mean /= (high - low)

    variance = 0
    for point in (bt.dims*(low-1) + dimension):bt.dims:(bt.dims*(high-1))
      variance += (bt.centers[point] - mean) * (bt.centers[point] - mean);
    end
    if (variance > max_variance)
      max_variance = variance;
      max_dim = dimension;
    end
  end

  #println("most_spread_coord -- max_var=$(max_variance), return=$(max_dim)")
  return max_dim;
end

# straight from CLR, the unrandomized partition algorithm for
# quicksort.  Partitions the leaves from low to high inclusive around
# a random pivot in the given dimension.  Does not affect non-leaf
# nodes, but does relabel the leaves from low to high.
function partition!(bt::BallTree, dimension::Int64, low::Int64, high::Int64)
  pivot = low;  # not randomized, could set pivot to a random element

  while (low < high)
    while(bt.centers[bt.dims*(high-1) + dimension] >= bt.centers[bt.dims*(pivot-1) + dimension])
      high-=1
    end
    while(bt.centers[bt.dims*(low-1) + dimension] < bt.centers[bt.dims*(pivot-1) + dimension])
      low+=1
    end

    bt.swapHandle(bt, low, high)
    pivot = high
  end

  return high;
end

# Function to partition the data into two (equal-sized or near as possible)
#   sets, one of which is uniformly greater than the other in the given
#   dimension.
function select!(bt::BallTree, dimension::Int64, position::Int64, low::Int64, high::Int64)
    m = 0
    r = 0
    i = 0
  while low < high
    r = (floor(Int64,(low + high)/2))
    swap!(bt.data, r, low)
    m = low;
    for i in (low):high
      if (bt.centers[dimension+bt.dims*(i-1)] < bt.centers[dimension+bt.dims*(low-1)])
        m+=1
        swap!(bt.data, m, i);
      end
    end
    swap!(bt.data, low, m);
    if (m <= position) low=m+1 end
    if (m >= position) high=m-1 end
  end
  return Union{}
end

# Calculate the statistics of level "root" based on the statistics of
#   its left and right children.
function calcStatsBall!(bt::BallTree, root::Int64)
  #println("calcStatsBall! -- root=$(root)")
  Ni = 0
  NiL = 0
  NiR = 0
  d = 0

  leftI = left(bt, root)
  rightI=right(bt, root);   # get children indices
  if (!(validIndex(bt, leftI)) || !(validIndex(bt, rightI)))
    return Union{} # nothing to do if this
  end           #   isn't a parent node

  # figure out the center and ranges of this ball based on it's children
  maxi = 0.
  mini = 0.
  for d=1:bt.dims
    a = center(bt, leftI, d) + rangeB(bt, leftI, d)
    b = center(bt, rightI, d) + rangeB(bt, rightI, d)
    #@show (d, leftI, rightI, a, b)
    if (a > b)
      maxi = center(bt, leftI, d) + rangeB(bt, leftI, d)
    else
      maxi = center(bt, rightI, d) + rangeB(bt, rightI, d)
    end

    if (center(bt, leftI, d) - rangeB(bt, leftI, d) < center(bt, rightI, d) - rangeB(bt, rightI, d))
      mini = center(bt, leftI, d) - rangeB(bt, leftI, d)
    else
      mini = center(bt, rightI, d) - rangeB(bt, rightI, d)
    end

    #@show (d, mini,maxi);
    bt.centers[(root-1)*bt.dims+d] = (maxi+mini) / 2.0;
    bt.ranges[(root-1)*bt.dims+d] = (maxi-mini) / 2.0;
  end

  # if the left ball is the same as the right ball (should only
  # happen when calling the function directly with the same argument
  # twice), don't count the weight twice
  if (leftI != rightI)
    bt.weights[root] = bt.weights[leftI] + bt.weights[rightI];
  else
    bt.weights[root] = bt.weights[leftI]
  end
  return Union{}
end

# Given the leaves, build the rest of the tree from the top down.
# Split the leaves along the most spread coordinate, build two balls
# out of those, and then build a ball around those two children.
function buildBall!(bt::BallTree, low::Int64, high::Int64, root::Int64)
  #println("buildBall! -- (low, high, root)=$((low, high, root))")
  # special case for N=1 trees
  if (low == high)
    bt.lowest_leaf[root] = low;
    bt.highest_leaf[root] = high;
    bt.left_child[root] = low;

    # point right child to the same as left for calc stats, and then
    # point it to the correct NO_CHILD afterwards.  kinda kludgey
    bt.right_child[root] = high;
    calcStats!(bt.data, root)
    bt.right_child[root] = NO_CHILD;
    return Union{}
  end

  #BallTree::index coord, split, left, right;
  coord = most_spread_coord(bt, low, high); # find dimension of widest spread

  # split the current leaves into two groups, to build balls on them.
  # Choose the most spread coordinate to split them on, and make sure
  # there are the same number of points in each (+-1 for round off
  # error).
  split = (floor(Int64,(low + high) / 2))
  #@show coord, split, low, high
  select!(bt, coord, split, low, high)

  # an alternative is to use partition, but that doesn't deal well
  # with repeated numbers and it doesn't split into balanced sets.
  # split = partition(coord, low, high);

  # if the left sub-tree is just one leaf, don't make a new non-leaf
  # node for it, just point left_idx directly to the leaf itself.
  if (split <= low)
    left = low
  else
    left = bt.next;
    bt.next+=1
  end

  # same for the right
  if (split+1 >= high)
    right = high
  else
    right = bt.next
    bt.next+=1
  end

  bt.lowest_leaf[root]  = low;
  bt.highest_leaf[root] = high;
  bt.left_child[root]   = left;
  bt.right_child[root]  = right;

  # build sub-trees if necessary
  if(left != low)
    buildBall!(bt, low, split, left)
  end
  if(right != high)
    buildBall!(bt, split+1, high, right)
  end

  calcStats!(bt.data, root);
  return Union{}
end

# Public method to build the tree, just calls the private method with
# the proper starting arguments.
function buildTree!(bt::BallTree)
  #println("buildTree!(::BallTree) -- is running")
  i=bt.num_points
  for j in 1:bt.num_points
    for k in 1:bt.dims
      bt.ranges[i*bt.dims+k] = 0
    end
    i+=1
    bt.lowest_leaf[i] = i
    bt.highest_leaf[i] = i
    bt.left_child[i] = i
    bt.right_child[i] = NO_CHILD #int64(uint32(NO_CHILD))
    bt.permutation[i] = j
  end
  bt.next = 2

  buildBall!(bt, bt.num_points+1, 2*bt.num_points, 1); # chgd for indexing 1
  return Union{}
end

# New BallTree
function makeBallTree(_pointsMatrix::Array{Float64,2}, _weights::Array{Float64,1}, suppressBuildTree=false)
  # get fields from input arguments
  Nd = size(_pointsMatrix,1);
  Np = size(_pointsMatrix,2);

  _points = reshape(_pointsMatrix, Nd*Np, 1)
  centers = zeros(Nd*2*Np)
  ranges = zeros(Nd*2*Np)
  weights = zeros(2*Np)
  centers[(Np*Nd+1):end] = _points[1:end]
  weights[(Np+1):end] = _weights[1:end]

  bt = BallTree(Nd, Np, centers, ranges, weights,
                  ones(Int64,2*Np), ones(Int64,2*Np), ones(Int64,2*Np),
                  ones(Int64,2*Np), zeros(Int64,2*Np),
                  0, swapBall!, calcStatsBall!, [])
  bt.data = bt

  if (Np > 0 && suppressBuildTree!=true)
    buildTree!(bt)
  end
  return bt
end

function printBallTree(bt::BallTree)
    @show round(bt.centers,15);
    @show round(bt.weights,15);
    @show round(bt.ranges,15);
    @show bt.left_child;
    @show bt.right_child;
    @show bt.highest_leaf;
    @show bt.lowest_leaf;
    @show bt.permutation;
    return Union{}
end

function test01()
  ## test point 01
  ## matlab results
  #centers=[30]=4.4, 3.6, 5, 3.2, 0.18, 2, 5.5, 6.5, 7.5, 2.6, 0.18, 2.5, 0, 0, 0, 1.9, 1.2, 3, 3.3, -0.81, 2, 4.6, -0.56, 1, 4, 5, 6, 7, 8, 9,
  #weights=[10]=1, 0.6, 0.4, 0.4, 0, 0.2, 0.2, 0.2, 0.2, 0.2,
  #ranges=[30]=2.6, 4.4, 4, 1.4, 0.98, 1, 1.5, 1.5, 1.5, 0.7, 0.98, 0.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  #highest_leaf=[10]=9, 7, 9, 6, 0, 5, 6, 7, 8, 9,
  #lowest_leaf=[10]=5, 5, 8, 5, 0, 5, 6, 7, 8, 9,
  #permutation=[10]=0, 0, 0, 0, 0, 2, 1, 0, 3, 4,
  mus = [[4.6173,    3.2641,    1.8729, 4, 7 ]',
   [-0.5592,   -0.8088,    1.1610, 5, 8]',
   [1.,2.,3, 6, 9]']

  bt = makeBallTree(mus, 0.2*ones(5))
  printBallTree(bt);
end


using StatsModels: AbstractTerm

struct RandTerm <: AbstractTerm
    var::Char
    data::Char
end

ran(s::Char, d::Char) = RandTerm(s, d)

struct PartRegTerm <: AbstractTerm
    mat::Char
    regSize::Int
    path::String
end

PR(s::Char, d::Int, p::String) = PartRegTerm(s, d, p)


## Related to constraint handlers

"""
Recover Julia object which is attached to user constraint (via constraint data).
"""
function user_constraint(cons_::Ptr{SCIP_CONS})
    @assert cons_ != C_NULL
    consdata_::Ptr{SCIP.SCIP_CONSDATA} = SCIPconsGetData(cons_)
    return unsafe_pointer_to_objref(consdata_)
end

"""
Extract solution values for given variables.
"""
function sol_values(o::Optimizer, vars::AbstractArray{VI}, sol::Ptr{SCIP_SOL}=C_NULL)
    return [SCIPgetSolVal(o, sol, var(o, vi)) for vi in vars]
end


# Kernels for unary array operations

fp = open("cuda1.cu","w")
#using Knet: unary_ops

include("gamma.jl")
print(fp,cuda1gammafamily())

function cuda1src(f, j=f, ex="$f(xi)"; seperate_impl=false, BLK=256, THR=256)
    sprint() do s
        for (T,F) in [("float","$(f)_32"),("double","$(f)_64")]
            seperate_impl && (ex = "$F(xi)")
            print(s,
"""
__global__ void _$F(int n, $T *x, $T *y) {
  int i = threadIdx.x + blockIdx.x * blockDim.x;
  while (i < n) {
    $T xi = x[i];
    y[i] = $ex;
    i += blockDim.x * gridDim.x;
  }
}
extern "C" {
  $DLLEXPORT void $F(int n, $T *x, $T *y) {
    if (n>0) _$F<<<$BLK,$THR>>>(n,x,y);
  }
}
""")
        end
    end
end

seperate_impl_ops = ["gamma_impl", "digamma_impl", "trigamma_impl"]
for a in unary_ops
    if !isa(a,Tuple); a=(a,); end
    print(fp,cuda1src(a...; seperate_impl=(a[1] in seperate_impl_ops)))
end

# Kernels used by setindex! and getindex: fill, xfill, xcopy:

function cuda1fill(; BLK=256, THR=256)
    sprint() do s
        for (T,F) in [("float","32"),("double","64")]
            print(s,
"""
__global__ void _fill_$F(int n, $T x, $T *y) {
  int i = threadIdx.x + blockIdx.x * blockDim.x;
  while (i < n) {
    y[i] = x;
    i += blockDim.x * gridDim.x;
  }
}
extern "C" {
  $DLLEXPORT void fill_$F(int n, $T x, $T *y) {
    if (n>0) _fill_$F<<<$BLK,$THR>>>(n,x,y);
  }
}
""")
        end
    end
end

print(fp,cuda1fill())

function cuda1xfill(; BLK=256, THR=256)
    sprint() do s
        for (T,F) in [("float","32"),("double","64")]
            print(s,
"""
__global__ void _xfill_$F(int nrows, int ncols, $T x, $T *y, int incy) {
  int row, col, yidx;
  int i = threadIdx.x + blockIdx.x * blockDim.x;
  while (1) {
    row = i % nrows;
    col = i / nrows;
    if (col >= ncols) break;
    yidx = row + col * incy;
    y[yidx] = x;
    i += blockDim.x * gridDim.x;
  }
}
extern "C" {
  $DLLEXPORT void xfill_$F(int nrows, int ncols, $T x, $T *y, int incy) {
    if (nrows>0 && ncols>0) _xfill_$F<<<$BLK,$THR>>>(nrows, ncols, x, y, incy);
  }
}
""")
        end
    end
end

print(fp,cuda1xfill())

function cuda1xcopy(; BLK=256, THR=256)
"""
__global__ void _xcopy(int nrows, int ncols, const char *x, int incx, char *y, int incy) {
  int row, col, xidx, yidx;
  int i = threadIdx.x + blockIdx.x * blockDim.x;
  while (1) {
    row = i % nrows;
    col = i / nrows;
    if (col >= ncols) break;
    xidx = row + col * incx;
    yidx = row + col * incy;
    y[yidx] = x[xidx];
    i += blockDim.x * gridDim.x;
  }
}
extern "C" {
  $DLLEXPORT void xcopy(int nrows, int ncols, const void *x, int incx, void *y, int incy) {
    if (nrows>0 && ncols>0) _xcopy<<<$BLK,$THR>>>(nrows,ncols,(char*)x,incx,(char*)y,incy);
  }
}
"""
end

print(fp,cuda1xcopy())

### Kernels for permutedims by Ekrem Emre Yurdakul 2017-02-27

function permutedims2Dsrc(f,i1,i2; BLK=256,THR=256)
    sprint() do s
        for (T,F) in [("float","$(f)32"),("double","$(f)64")]
            print(s,
"""
__global__ void _$(F)($T* x, int dimx1, int dimx2, $T* y, int dimy1) {
  for (int v = threadIdx.x + blockIdx.x * blockDim.x; v < dimx1*dimx2; v += blockDim.x * gridDim.x) {

    //From 1D to 2D indices
    int i = v % dimx1;
    int j = (v-i) / dimx1;

    //Calculate destination
    int destIndex = $i1 + $i2*dimy1;
    y[destIndex] = x[v];
	}
}
extern "C" {
  $DLLEXPORT void $(F)($T* x, int dimx1, int dimx2, $T* y, int dimy1) {
    _$(F)<<<$BLK,$THR>>>(x,dimx1,dimx2,y,dimy1);
  }
}
""")
        end
    end
end

function permutedims3Dsrc(f,i1,i2,i3; BLK=256,THR=256)
    sprint() do s
        for (T,F) in [("float","$(f)32"),("double","$(f)64")]
            print(s,
"""
__global__ void _$(F)($T* x, int dimx1, int dimx2, int dimx3, $T* y, int dimy1, int dimy2) {
  for (int v = threadIdx.x + blockIdx.x * blockDim.x; v < dimx1*dimx2*dimx3; v += blockDim.x * gridDim.x) {

    //From 1D to 3D indices
    int i = v % dimx1;
    int j = ((v-i) / dimx1) % dimx2;
    int k = (v-i-j*dimx1) / (dimx1*dimx2);

    //Calculate destination
    int destIndex = $i1 + $i2*dimy1 + $i3*dimy1*dimy2;
    y[destIndex] = x[v];
	}
}
extern "C" {
  $DLLEXPORT void $(F)($T* x, int dimx1, int dimx2, int dimx3, $T* y, int dimy1, int dimy2) {
    _$(F)<<<$BLK,$THR>>>(x,dimx1,dimx2,dimx3,y,dimy1,dimy2);
  }
}
""")
        end
    end
end

function permutedims4Dsrc(f,i1,i2,i3,i4; BLK=256,THR=256)
    sprint() do s
        for (T,F) in [("float","$(f)32"),("double","$(f)64")]
            print(s,
"""
__global__ void _$(F)($T* x, int dimx1, int dimx2, int dimx3, int dimx4, $T* y, int dimy1, int dimy2, int dimy3) {
  for (int v = threadIdx.x + blockIdx.x * blockDim.x; v < dimx1*dimx2*dimx3*dimx4; v += blockDim.x * gridDim.x) {

    //From 1D to 4D indices
    int i = v % dimx1;
    int j = ((v-i) / dimx1) % dimx2;
    int k = ((v-i-j*dimx1) / (dimx1*dimx2)) % dimx3;
    int l = (v-i-j*dimx1-k*dimx1*dimx2) / (dimx1*dimx2*dimx3);

    //Calculate destination
    int destIndex = $i1 + $i2*dimy1 + $i3*dimy1*dimy2 + $i4*dimy1*dimy2*dimy3;
    y[destIndex] = x[v];
	}
}
extern "C" {
  $DLLEXPORT void $(F)($T* x, int dimx1, int dimx2, int dimx3, int dimx4, $T* y, int dimy1, int dimy2, int dimy3) {
    _$(F)<<<$BLK,$THR>>>(x,dimx1,dimx2,dimx3,dimx4,y,dimy1,dimy2,dimy3);
  }
}
""")
        end
    end
end

function permutedims5Dsrc(f,i1,i2,i3,i4,i5; BLK=256,THR=256)
    sprint() do s
        for (T,F) in [("float","$(f)32"),("double","$(f)64")]
            print(s,
"""
__global__ void _$(F)($T* x, int dimx1, int dimx2, int dimx3, int dimx4, int dimx5, $T* y, int dimy1, int dimy2, int dimy3, int dimy4) {
  for (int v = threadIdx.x + blockIdx.x * blockDim.x; v < dimx1*dimx2*dimx3*dimx4*dimx5; v += blockDim.x * gridDim.x) {

    //From 1D to 5D indices
    int i = v % dimx1;
    int j = ((v-i) / dimx1) % dimx2;
    int k = ((v-i-j*dimx1) / (dimx1*dimx2)) % dimx3;
    int l = ((v-i-j*dimx1-k*dimx1*dimx2) / (dimx1*dimx2*dimx3)) % dimx4;
    int m = (v-i-j*dimx1-k*dimx1*dimx2-l*dimx1*dimx2*dimx3) / (dimx1*dimx2*dimx3*dimx4);

    //Calculate destination
    int destIndex = $i1 + $i2*dimy1 + $i3*dimy1*dimy2 + $i4*dimy1*dimy2*dimy3 + $i5*dimy1*dimy2*dimy3*dimy4;
    y[destIndex] = x[v];
	}
}
extern "C" {
  $DLLEXPORT void $(F)($T* x, int dimx1, int dimx2, int dimx3, int dimx4, int dimx5, $T* y, int dimy1, int dimy2, int dimy3, int dimy4) {
    _$(F)<<<$BLK,$THR>>>(x,dimx1,dimx2,dimx3,dimx4,dimx5,y,dimy1,dimy2,dimy3,dimy4);
  }
}
""")
        end
    end
end

# avoid dependency, copy permutations from julia4 base
# using Combinatorics

import Base: iterate, length
struct Perms; a; end
perms(a) = Perms(a)
length(p::Perms) = factorial(length(p.a))
#start(p::Perms) = [1:length(p.a);]
#done(p::Perms, s) = !isempty(s) && s[1] > length(p.a)
function iterate(p::Perms, s=[1:length(p.a);])
    if !isempty(s) && s[1] > length(p.a); return nothing; end
    perm = [p.a[si] for si in s]
    if length(p.a) == 0
        # special case to generate 1 result for len==0
        return (perm,[1])
    end
    s = copy(s)
    k = length(s)-1
    while k > 0 && s[k] > s[k+1];  k -= 1;  end
    if k == 0
        s[1] = length(s)+1   # done
    else
        l = length(s)
        while s[k] >= s[l];  l -= 1;  end
        s[k],s[l] = s[l],s[k]
        reverse!(s,k+1)
    end
    (perm,s)
end

function cuda1permutedims()
  cudaPerms = [permutedims2Dsrc,permutedims3Dsrc,permutedims4Dsrc,permutedims5Dsrc]
  for i=2:5
      dims = collect(perms(1:i))
      indnames = collect(perms(["i","j","k","l","m"][1:i]))
      for j=1:length(dims)
          fname = string("permutedims_",i,"D_",join(dims[j],"_"),"_")
          print(fp,cudaPerms[i-1](fname,indnames[j]...))
      end
  end
end

cuda1permutedims()

function cuda1icat(; BLK=256, THR=256)
    sprint() do s
        for (T,F) in [("float","32"),("double","64")]
            print(s,
"""
__global__ void _icat_$F(int nrows, int ncols, $T **x, $T *y) {
  int row, col, yidx;
  int i = threadIdx.x + blockIdx.x * blockDim.x;
  while (1) {
    row = i % nrows;
    col = i / nrows;
    if (col >= ncols) break;
    yidx = row + col * nrows;
    y[yidx] = x[col][row];
    i += blockDim.x * gridDim.x;
  }
}
extern "C" {
  $DLLEXPORT void icat_$F(int nrows, int ncols, $T **x, $T *y) {
    $T **xx;
    if (nrows>0 && ncols>0) {
      size_t s = ncols * sizeof($T *);
      cudaMalloc(&xx, s);
      cudaMemcpy(xx, x, s, cudaMemcpyHostToDevice);
      _icat_$F<<<$BLK,$THR>>>(nrows, ncols, xx, y);
      cudaFree(xx);
    }
  }
}
""")
        end
    end
end

print(fp,cuda1icat())

# This is for missing double atomicAdd()
print(fp,"""
static __inline__ __device__ float atomicAdd2(float *address, float val) {
  return atomicAdd(address, val);
}
#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ >= 600
static __inline__ __device__ double atomicAdd2(double *address, double val) {
  return atomicAdd(address, val);
}
#else
static __inline__ __device__ double atomicAdd2(double *address, double val) {
  unsigned long long int* address_as_ull = (unsigned long long int*)address;
  unsigned long long int old = *address_as_ull, assumed;
  if (val==0.0)
    return __longlong_as_double(old);
  do {
    assumed = old;
    old = atomicCAS(address_as_ull, assumed, __double_as_longlong(val +__longlong_as_double(assumed)));
  } while (assumed != old);
  return __longlong_as_double(old);
}
#endif
""")

function cuda1getcols(; BLK=256, THR=256)
    sprint() do s
        for (T,F) in [("float","32"),("double","64")]
            print(s,
"""
__global__ void _getcols_$F(int xrows, int xcols, int ncols, int *cols, $T *x, $T *y) {
  int row, col, xidx;
  int yidx = threadIdx.x + blockIdx.x * blockDim.x;
  while (1) {
    row = yidx % xrows;
    col = yidx / xrows;
    if (col >= ncols) break;
    xidx = row + (cols[col]-1) * xrows;
    y[yidx] = x[xidx];
    yidx += blockDim.x * gridDim.x;
  }
}
__global__ void _setcols_$F(int xrows, int xcols, int ncols, int *cols, $T *x, $T *y) {
  int row, col, xidx;
  int yidx = threadIdx.x + blockIdx.x * blockDim.x;
  while (1) {
    row = yidx % xrows;
    col = yidx / xrows;
    if (col >= ncols) break;
    xidx = row + (cols[col]-1) * xrows;
    x[xidx] = y[yidx];
    yidx += blockDim.x * gridDim.x;
  }
}
__global__ void _addcols_$F(int xrows, int xcols, int ncols, int *cols, $T *x, $T *y) {
  int row, col, xidx;
  int yidx = threadIdx.x + blockIdx.x * blockDim.x;
  while (1) {
    row = yidx % xrows;
    col = yidx / xrows;
    if (col >= ncols) break;
    xidx = row + (cols[col]-1) * xrows;
    atomicAdd2(&x[xidx], y[yidx]);
    yidx += blockDim.x * gridDim.x;
  }
}
__global__ void _setcol1_$F(int xrows, int xcols, int ncols, int *cols, $T *x, $T y) {
  int row, col, xidx;
  int yidx = threadIdx.x + blockIdx.x * blockDim.x;
  while (1) {
    row = yidx % xrows;
    col = yidx / xrows;
    if (col >= ncols) break;
    xidx = row + (cols[col]-1) * xrows;
    x[xidx] = y;
    yidx += blockDim.x * gridDim.x;
  }
}
__global__ void _getrows_$F(int xrows, int xcols, int nrows, int *rows, $T *x, $T *y) {
  int row, col, xidx;
  int yidx = threadIdx.x + blockIdx.x * blockDim.x;
  while (1) {
    row = yidx % nrows;
    col = yidx / nrows;
    if (col >= xcols) break;
    xidx = rows[row] - 1 + col * xrows;
    y[yidx] = x[xidx];
    yidx += blockDim.x * gridDim.x;
  }
}
__global__ void _setrows_$F(int xrows, int xcols, int nrows, int *rows, $T *x, $T *y) {
  int row, col, xidx;
  int yidx = threadIdx.x + blockIdx.x * blockDim.x;
  while (1) {
    row = yidx % nrows;
    col = yidx / nrows;
    if (col >= xcols) break;
    xidx = rows[row] - 1 + col * xrows;
    x[xidx] = y[yidx];
    yidx += blockDim.x * gridDim.x;
  }
}
__global__ void _addrows_$F(int xrows, int xcols, int nrows, int *rows, $T *x, $T *y) {
  int row, col, xidx;
  int yidx = threadIdx.x + blockIdx.x * blockDim.x;
  while (1) {
    row = yidx % nrows;
    col = yidx / nrows;
    if (col >= xcols) break;
    xidx = rows[row] - 1 + col * xrows;
    atomicAdd2(&x[xidx], y[yidx]);
    yidx += blockDim.x * gridDim.x;
  }
}
__global__ void _setrow1_$F(int xrows, int xcols, int nrows, int *rows, $T *x, $T y) {
  int row, col, xidx;
  int yidx = threadIdx.x + blockIdx.x * blockDim.x;
  while (1) {
    row = yidx % nrows;
    col = yidx / nrows;
    if (col >= xcols) break;
    xidx = rows[row] - 1 + col * xrows;
    x[xidx] = y;
    yidx += blockDim.x * gridDim.x;
  }
}
__global__ void _getents_$F(int n, int *ents, $T *x, $T *y) {
  int i = threadIdx.x + blockIdx.x * blockDim.x;
  while (i < n) {
    y[i] = x[ents[i]-1];
    i += blockDim.x * gridDim.x;
  }
}
__global__ void _setents_$F(int n, int *ents, $T *x, $T *y) {
  int i = threadIdx.x + blockIdx.x * blockDim.x;
  while (i < n) {
    x[ents[i]-1] = y[i];
    i += blockDim.x * gridDim.x;
  }
}
__global__ void _addents_$F(int n, int *ents, $T *x, $T *y) {
  int i = threadIdx.x + blockIdx.x * blockDim.x;
  while (i < n) {
    atomicAdd2(&x[ents[i]-1], y[i]);
    i += blockDim.x * gridDim.x;
  }
}
__global__ void _setent1_$F(int n, int *ents, $T *x, $T y) {
  int i = threadIdx.x + blockIdx.x * blockDim.x;
  while (i < n) {
    x[ents[i]-1] = y;
    i += blockDim.x * gridDim.x;
  }
}
extern "C" {
$DLLEXPORT void getcols_$F(int xrows, int xcols, int ncols, int *cols, $T *x, $T *y)
{ if (ncols>0 && xrows>0 && xcols>0) _getcols_$F<<<$BLK,$THR>>>(xrows,xcols,ncols,cols,x,y); }
$DLLEXPORT void setcols_$F(int xrows, int xcols, int ncols, int *cols, $T *x, $T *y)
{ if (ncols>0 && xrows>0 && xcols>0) _setcols_$F<<<$BLK,$THR>>>(xrows,xcols,ncols,cols,x,y); }
$DLLEXPORT void addcols_$F(int xrows, int xcols, int ncols, int *cols, $T *x, $T *y)
{ if (ncols>0 && xrows>0 && xcols>0) _addcols_$F<<<$BLK,$THR>>>(xrows,xcols,ncols,cols,x,y); }
$DLLEXPORT void setcol1_$F(int xrows, int xcols, int ncols, int *cols, $T *x, $T  y)
{ if (ncols>0 && xrows>0 && xcols>0) _setcol1_$F<<<$BLK,$THR>>>(xrows,xcols,ncols,cols,x,y); }
$DLLEXPORT void getrows_$F(int xrows, int xcols, int nrows, int *rows, $T *x, $T *y)
{ if (nrows>0 && xrows>0 && xcols>0) _getrows_$F<<<$BLK,$THR>>>(xrows,xcols,nrows,rows,x,y); }
$DLLEXPORT void setrows_$F(int xrows, int xcols, int nrows, int *rows, $T *x, $T *y)
{ if (nrows>0 && xrows>0 && xcols>0) _setrows_$F<<<$BLK,$THR>>>(xrows,xcols,nrows,rows,x,y); }
$DLLEXPORT void addrows_$F(int xrows, int xcols, int nrows, int *rows, $T *x, $T *y)
{ if (nrows>0 && xrows>0 && xcols>0) _addrows_$F<<<$BLK,$THR>>>(xrows,xcols,nrows,rows,x,y); }
$DLLEXPORT void setrow1_$F(int xrows, int xcols, int nrows, int *rows, $T *x, $T  y)
{ if (nrows>0 && xrows>0 && xcols>0) _setrow1_$F<<<$BLK,$THR>>>(xrows,xcols,nrows,rows,x,y); }
$DLLEXPORT void getents_$F(int n, int *ents, $T *x, $T *y)
{ if (n>0) _getents_$F<<<$BLK,$THR>>>(n,ents,x,y); }
$DLLEXPORT void setents_$F(int n, int *ents, $T *x, $T *y)
{ if (n>0) _setents_$F<<<$BLK,$THR>>>(n,ents,x,y); }
$DLLEXPORT void addents_$F(int n, int *ents, $T *x, $T *y)
{ if (n>0) _addents_$F<<<$BLK,$THR>>>(n,ents,x,y); }
$DLLEXPORT void setent1_$F(int n, int *ents, $T *x, $T  y)
{ if (n>0) _setent1_$F<<<$BLK,$THR>>>(n,ents,x,y); }
}
""")
        end
    end
end

print(fp,cuda1getcols())

# Dropout

function cuda1dropout(; BLK=256, THR=256)
    sprint() do s
        for (T,F) in [("float","32"),("double","64")]
            print(s,
"""
__global__ void _dropout_$F(int n, $T p, $T q, $T *x, $T *y) {
  int i = threadIdx.x + blockIdx.x * blockDim.x;
  while (i < n) {
    if (y[i] > p) {
      y[i] = x[i] * q;
    } else {
      y[i] = 0;
    }
    i += blockDim.x * gridDim.x;
  }
}
__global__ void _dropback_$F(int n, $T q, $T *y, $T *dy, $T *dx) {
  int i = threadIdx.x + blockIdx.x * blockDim.x;
  while (i < n) {
    if (y[i] == 0) {
        dx[i] = 0;
    } else {
        dx[i] = dy[i] * q;
    }
    i += blockDim.x * gridDim.x;
  }
}
extern "C" {
  $DLLEXPORT void dropout_$F(int n, $T p, $T *x, $T *y) {
    if (n>0) _dropout_$F<<<$BLK,$THR>>>(n,p,1.0/(1.0-p),x,y);
  }
  $DLLEXPORT void dropback_$F(int n, $T p, $T *x, $T *y, $T *dy, $T *dx) {
    if (n>0) _dropback_$F<<<$BLK,$THR>>>(n,1.0/(1.0-p),y,dy,dx);
  }
}
""")
        end
    end
end

print(fp,cuda1dropout())

# This is still too slow compared to concat on cpu and copy to gpu
# Tested for 25 arrays of 200
function cuda1concat(; BLK=256, THR=256)
    sprint() do s
        for (T,F) in [("float","32"),("double","64")]
            print(s,
"""
__global__ void _concat_$F(int narrays, int *starts, int *lengths, $T **x, $T *y) {
  int array = blockIdx.x;
  int nelts = lengths[array];
  int offset = starts[array];
  for (int i = threadIdx.x; i < nelts; i += blockDim.x) {
    y[i+offset] = x[array][i];
  }
}
extern "C" {
  // julia is responsible for copying args to gpu
  $DLLEXPORT void concat_$F(int narrays, int *starts, int *lengths, $T **x, $T *y) {
    _concat_$F<<<narrays,$THR>>>(narrays, starts, lengths, x, y);
  }
}
""")
        end
    end
end

print(fp,cuda1concat())

close(fp)

# Here is the test script for cuda1concat:
# using Knet, BenchmarkTools
# using Knet: @knet8

# for S in (32,64); T = Symbol("Float$S"); F = "concat_$S"
# @eval function concat(A::KnetArray{$T}...)
#     nargs = length(A)
#     S = Array{Int32}(nargs)
#     L = Array{Int32}(nargs)
#     nelts = 0
#     @inbounds for i in 1:nargs
#         n = length(A[i])
#         S[i] = nelts
#         L[i] = n
#         nelts += n
#     end
#     S = KnetArray(S)
#     L = KnetArray(L)
#     X = KnetArray([map(pointer,A)...])
#     Y = KnetArray{$T}(nelts)
#     @knet8($F,(Cint,Ptr{Cint},Ptr{Cint},Ptr{Ptr{$T}},Ptr{$T}),nargs,S,L,X,Y)
#     return Y
# end
# end

# a = [ rand(Float32,200) for i=1:25 ]
# k = map(KnetArray,a)
# @show vcat(a...) == vcat(k...)
# @show vcat(a...) == concat(k...)
# @show @benchmark vcat(a...)
# @show @benchmark vcat(k...)
# @show @benchmark concat(k...)
# @show @benchmark KnetArray(vcat(a...))


countprimes(stop::ConvT; tuplet::Int = 1) = countprimes(conv128(stop),tuplet)
#countprimes(stop::ConvT) = countprimes(conv128(stop)) ## no sense
function countprimes(stop; tuplet::Int = 1)
    if tuplet == 1
        _countprimes(stop)
    elseif tuplet == 2
        countprimes2(stop)
    elseif tuplet == 3
        countprimes3(stop)
    elseif tuplet == 4
        countprimes4(stop)
    elseif tuplet == 5
        countprimes5(stop)
    elseif tuplet == 6
        countprimes6(stop)
    else
        error("tuplet must be between 1 and 6")
    end
end

countprimes(start::ConvT, stop::ConvT; tuplet::Int = 1) = countprimes(conv128(start),conv128(stop),tuplet)
#countprimes(start::ConvT, stop::ConvT) = countprimes(conv128(start),conv128(stop))
function countprimes(start,stop; tuplet::Int = 1, alg::Symbol = :tabsieve )
    if tuplet == 1
        if alg == :next || stop > stoplimit
            countprimesb(start,stop)
        elseif alg == :nexta
            countprimesc(start,stop)
        elseif alg == :tabsieve
            _countprimes(start,stop)
        elseif alg == :sieve
            ntcountprimes(start,stop)
        else
            error("algorithm must be one of :sieve, :next, :nexta, :tabsieve")
        end
    elseif tuplet == 2
        countprimes2(start,stop)
    elseif tuplet == 3
        countprimes3(start,stop)
    elseif tuplet == 4
        countprimes4(start,stop)
    elseif tuplet == 5
        countprimes5(start,stop)
    elseif tuplet == 6
        countprimes6(start,stop)
    else
        error("tuplet must be between 1 and 6")
    end
end

scountprimes(stop::ConvT; tuplet::Int = 1) = scountprimes(conv128(stop),tuplet)
#scountprimes(stop::ConvT) = scountprimes(conv128(stop))
function scountprimes(stop; tuplet::Int = 1)
    if tuplet == 1
        _scountprimes(stop)
    elseif tuplet == 2
        scountprimes2(stop)
    elseif tuplet == 3
        scountprimes3(stop)
    elseif tuplet == 4
        scountprimes4(stop)
    elseif tuplet == 5
        scountprimes5(stop)
    elseif tuplet == 6
        scountprimes6(stop)
    else
        error("tuplet must be between 1 and 6")
    end
end

scountprimes(start::ConvT, stop::ConvT; tuplet::Int = 1) = scountprimes(conv128(start),conv128(stop),tuplet)
#scountprimes(start::ConvT, stop::ConvT) = scountprimes(conv128(start),conv128(stop))
function scountprimes(start,stop; tuplet::Int = 1)
    if tuplet == 1
        _scountprimes(start,stop)
    elseif tuplet == 2
        scountprimes2(start,stop)
    elseif tuplet == 3
        scountprimes3(start,stop)
    elseif tuplet == 4
        scountprimes4(start,stop)
    elseif tuplet == 5
        scountprimes5(start,stop)
    elseif tuplet == 6
        scountprimes6(start,stop)
    else
        error("tuplet must be between 1 and 6")
    end
end

printprimes(stop::ConvT; tuplet::Int = 1) = printprimes(conv128(stop),tuplet)
#printprimes(stop::ConvT) = printprimes(conv128(stop))
function printprimes(stop; tuplet::Int = 1)
    if tuplet == 1
        _printprimes(stop)
    elseif tuplet == 2
        printprimes2(stop)
    elseif tuplet == 3
        printprimes3(stop)
    elseif tuplet == 4
        printprimes4(stop)
    elseif tuplet == 5
        printprimes5(stop)
    elseif tuplet == 6
        printprimes6(stop)
    else
        error("tuplet must be between 1 and 6")
    end
end

printprimes(start::ConvT, stop::ConvT; tuplet::Int = 1) = printprimes(conv128(start),conv128(stop),tuplet)
#printprimes(start::ConvT, stop::ConvT) = printprimes(conv128(start),conv128(stop))
function printprimes(start,stop; tuplet::Int = 1)
    if tuplet == 1
        _printprimes(start,stop)
    elseif tuplet == 2
        printprimes2(start,stop)
    elseif tuplet == 3
        printprimes3(start,stop)
    elseif tuplet == 4
        printprimes4(start,stop)
    elseif tuplet == 5
        printprimes5(start,stop)
    elseif tuplet == 6
        printprimes6(start,stop)
    else
        error("tuplet must be between 1 and 6")
    end
end