"""
    Libcint

Minimal wrap around the integral library libcint. This module exposes
libcint functions to the Julia interface.
"""
module Libcint

export cint1e_kin_sph!, cint1e_nuc_sph!, cint1e_ovlp_sph!, cint2c2e_sph!, cint2e_sph!, cint3c2e_sph!
export cint1e_ipkin_sph!, cint1e_ipnuc_sph!, cint1e_ipovlp_sph!, cint2e_ip1_sph!, cint1e_r_sph!

const LIBCINT = joinpath(@__DIR__, "../deps/libcint")

function CINTcgtos_spheric(bas_id, bas)
    @ccall LIBCINT.CINTcgtos_spheric(bas_id::Cint, bas::Ptr{Cint})::Cint
end

function cint1e_ovlp_sph!(buf, shls, atm, natm, bas, nbas, env)
    @ccall LIBCINT.cint1e_ovlp_sph(
                                    buf  :: Ptr{Cdouble},
                                    shls :: Ptr{Cint},
                                    atm  :: Ptr{Cint},
                                    natm :: Cint,
                                    bas  :: Ptr{Cint},
                                    nbas :: Cint,
                                    env  :: Ptr{Cdouble}
                                   )::Cvoid
end

function cint1e_kin_sph!(buf, shls, atm, natm, bas, nbas, env)
    @ccall LIBCINT.cint1e_kin_sph(
                                    buf  :: Ptr{Cdouble},
                                    shls :: Ptr{Cint},
                                    atm  :: Ptr{Cint},
                                    natm :: Cint,
                                    bas  :: Ptr{Cint},
                                    nbas :: Cint,
                                    env  :: Ptr{Cdouble}
                                   )::Cvoid
end

function cint1e_nuc_sph!(buf, shls, atm, natm, bas, nbas, env)
    @ccall LIBCINT.cint1e_nuc_sph(
                                    buf  :: Ptr{Cdouble},
                                    shls :: Ptr{Cint},
                                    atm  :: Ptr{Cint},
                                    natm :: Cint,
                                    bas  :: Ptr{Cint},
                                    nbas :: Cint,
                                    env  :: Ptr{Cdouble}
                                   )::Cvoid
end

function cint2e_sph!(buf, shls, atm, natm, bas, nbas, env)
    opt = Ptr{UInt8}(C_NULL)
    @ccall LIBCINT.cint2e_sph(
                                    buf  :: Ptr{Cdouble},
                                    shls :: Ptr{Cint},
                                    atm  :: Ptr{Cint},
                                    natm :: Cint,
                                    bas  :: Ptr{Cint},
                                    nbas :: Cint,
                                    env  :: Ptr{Cdouble},
                                    opt :: Ptr{UInt8},
                                   )::Cvoid
end

function cint2c2e_sph!(buf, shls, atm, natm, bas, nbas, env)
    opt = Ptr{UInt8}(C_NULL)
    @ccall LIBCINT.cint2c2e_sph(
                                    buf  :: Ptr{Cdouble},
                                    shls :: Ptr{Cint},
                                    atm  :: Ptr{Cint},
                                    natm :: Cint,
                                    bas  :: Ptr{Cint},
                                    nbas :: Cint,
                                    env  :: Ptr{Cdouble},
                                    opt :: Ptr{UInt8},
                                   )::Cvoid
end

function cint3c2e_sph!(buf, shls, atm, natm, bas, nbas, env)
    opt = Ptr{UInt8}(C_NULL)
    @ccall LIBCINT.cint3c2e_sph(
                                    buf  :: Ptr{Cdouble},
                                    shls :: Ptr{Cint},
                                    atm  :: Ptr{Cint},
                                    natm :: Cint,
                                    bas  :: Ptr{Cint},
                                    nbas :: Cint,
                                    env  :: Ptr{Cdouble},
                                    opt :: Ptr{UInt8},
                                   )::Cvoid
end

function cint1e_ipovlp_sph!(buf, shls, atm, natm, bas, nbas, env)
    @ccall LIBCINT.cint1e_ipovlp_sph(
                                    buf  :: Ptr{Cdouble},
                                    shls :: Ptr{Cint},
                                    atm  :: Ptr{Cint},
                                    natm :: Cint,
                                    bas  :: Ptr{Cint},
                                    nbas :: Cint,
                                    env  :: Ptr{Cdouble}
                                   )::Cvoid
end

function cint1e_ipkin_sph!(buf, shls, atm, natm, bas, nbas, env)
    @ccall LIBCINT.cint1e_ipkin_sph(
                                    buf  :: Ptr{Cdouble},
                                    shls :: Ptr{Cint},
                                    atm  :: Ptr{Cint},
                                    natm :: Cint,
                                    bas  :: Ptr{Cint},
                                    nbas :: Cint,
                                    env  :: Ptr{Cdouble}
                                   )::Cvoid
end

function cint1e_ipnuc_sph!(buf, shls, atm, natm, bas, nbas, env)
    @ccall LIBCINT.cint1e_ipnuc_sph(
                                    buf  :: Ptr{Cdouble},
                                    shls :: Ptr{Cint},
                                    atm  :: Ptr{Cint},
                                    natm :: Cint,
                                    bas  :: Ptr{Cint},
                                    nbas :: Cint,
                                    env  :: Ptr{Cdouble}
                                   )::Cvoid
end

function cint2e_ip1_sph!(buf, shls, atm, natm, bas, nbas, env)
    opt = Ptr{UInt8}(C_NULL)
    @ccall LIBCINT.cint2e_ip1_sph(
                                    buf  :: Ptr{Cdouble},
                                    shls :: Ptr{Cint},
                                    atm  :: Ptr{Cint},
                                    natm :: Cint,
                                    bas  :: Ptr{Cint},
                                    nbas :: Cint,
                                    env  :: Ptr{Cdouble},
                                    opt :: Ptr{UInt8},
                                   )::Cvoid
end

function cint1e_r_sph!(buf, shls, atm, natm, bas, nbas, env)
    @ccall LIBCINT.cint1e_r_sph(
                                    buf  :: Ptr{Cdouble},
                                    shls :: Ptr{Cint},
                                    atm  :: Ptr{Cint},
                                    natm :: Cint,
                                    bas  :: Ptr{Cint},
                                    nbas :: Cint,
                                    env  :: Ptr{Cdouble}
                                   )::Cvoid
end

end #module


module GrowingRegions

using Printf
import SearchBall, GeometryTypes
const sb = SearchBall
const gt = GeometryTypes

function init_strategies(config; kwargs...)
    # myPlayers
    ws = config.ws

    xlen, ylen = Int(ceil(config.field_size[1]))*10, Int(ceil(config.field_size[2]))*10
    config.global_communication["sight_regions"] = fill(0.0, (xlen, ylen))
    config.global_communication["x_raster"] = sb.raster(-config.field_size[1]/2, config.field_size[1]/2, xlen)
    config.global_communication["y_raster"] = sb.raster(-config.field_size[2]/2, config.field_size[2]/2, ylen)

    if length(ws.myPlayers) > 0
        # sb.add_check_button!(config, "draw function", "draw background")do widget
        #     if sb.get_value(widget, Bool)
        #         run_strategies(config, 0.0, :reset)
        #     else
        #         sb.clear_background!(config)
        #     end
        # end

    # radiobuttons = []
    #     for myIdx in eachindex(ws.myPlayers)
    #         myPlayer = ws.myPlayers[myIdx]
    #         rb = sb.add_radio_button!(config, "rb$myIdx", string(myIdx))do widget
    #             # if sb.get_manip_value(config, "draw function")
    #                 run_strategies(config, 0.0, :reset)
    #             # end
    #         end
    #         push!(radiobuttons, rb)
    #     end
    #     sb.add_radio_button_group!(config, "show function for", radiobuttons)
    end
    sb.add_check_button!(config, "sight_regions", "show sight_regions") do widget
        run_strategies(config, 0.0, :reset)
    end
    sb.add_check_button!(config, "with hfov", "with hfov") do widget
        run_strategies(config, 0.0, :reset)
    end

    add_shadowcheckbuttons(ws.myPlayers, "myPlayer", config)
    add_shadowcheckbuttons(ws.opponents, "opponent", config)

    return
end

function update_sightregions!(config::sb.Config, dt::Number)
    x_raster = config.global_communication["x_raster"]
    y_raster = config.global_communication["y_raster"]
    regs = config.global_communication["sight_regions"]
    for (idx, x) in enumerate(x_raster), (idy, y) in enumerate(y_raster)
        position = [x, y]
        # @warn position
        # @warn config.ws.myPlayers[1]
        # @warn sb.is_in_line_of_sight(config.ws, config.ws.myPlayers[1], position)
        if any(map(p -> sb.iswithin_hfov(p, position) && sb.is_in_line_of_sight(config.ws, p, position, excludes=[p]), config.ws.myPlayers))
            # @warn "inside"
            regs[idx, idy] = 0.0
        elseif regs[idx, idy] ≤ 5.0
            regs[idx, idy] += dt/10
        end
    end
    # @warn regs[1, 1]
    # @warn regs[1, end]
    # @warn regs[end, 1]
    # @warn regs[end, end]
    return
end

function add_shadowcheckbuttons(player::AbstractVector{<:sb.Robot}, name::AbstractString, config::sb.Config)
    for myplayer_idx in eachindex(player)
        sb.add_check_button!(config, string(name, "Shadow", myplayer_idx), string(name, " ", myplayer_idx)) do widget
            if !sb.get_value(widget, Bool)
                sb.show_text!(config, string(name, " ", myplayer_idx, " area:"), "")
            end
            run_strategies(config)
        end
        sb.init_text!(config, string(name, " ", myplayer_idx, " area:"), "")
    end
end

function handle_shadow(player::AbstractVector{<:sb.Robot}, name::AbstractString, config::sb.Config)
    for myplayer_idx in eachindex(player)
        sb.remove_graphics_filter!(config) do s
            startswith(s, string(name, " ", myplayer_idx, " area:"))
        end

        if !sb.get_manip_value(config, string(name, "Shadow", myplayer_idx))
            continue
        end
        shadows = sb.get_shadow_polygons(player[myplayer_idx], config, sb.get_manip_value(config, "with hfov"))

        shadow_area = sum([sb.polygon_area(s) for s in shadows])

        sb.show_text!(config, string(name, " ", myplayer_idx, " area:"), @sprintf("%.2fm²",shadow_area))

        for (idx, shadow) in enumerate(shadows)
            if isempty(shadow)
                continue
            end
            sb.show_graphic!(config, string(name, " ", myplayer_idx, " area:", idx), shadow[vcat(1:end, 1)], sb.GraphicLine(fill=true, fillcolor=[0.0, 0.0, 0.0, 0.2]))
        end
    end
end

function calc_myPlayers(config::sb.Config, dt::Number, set_type::Symbol, moved_object::Union{sb.Object{<:Number}, Nothing}=nothing)
    ws = config.ws

    handle_shadow(ws.myPlayers, "myPlayer", config)
    handle_shadow(ws.opponents, "opponent", config)

    if length(ws.myPlayers) > 0
        if dt > 0
            update_sightregions!(config, dt)
        end
        if sb.get_manip_value(config, "sight_regions")
            sb.send_background(config.global_communication["sight_regions"], [true], config, (0.0, 5.0), allways_repaint=true)
            # @warn "sended"
        else
            sb.clear_background!(config)
        end
        # myforfunction = parse(Int, sb.get_manip_value(config, "show function for"))

        # if set_type ∈ [:reset, :release] && sb.get_manip_value(config, "draw function") && moved_object!=ws.myPlayers[myforfunction] && (sb.get_manip_value(config, "with hfov") || length(ws.myPlayers) > 1 || length(ws.opponents) > 0)
        #     vals, ins = sb.sample(config, myforfunction, sb.get_manip_value(config, "with hfov"))
        #     sb.send_background(vals, ins, config)
        # end
    end

    return
end

function calc_opponend(config::sb.Config, dt::Number, set_type::Symbol, moved_object::Union{sb.Object{<:Number}, Nothing}=nothing)
    ws = config.ws

    # do stuff

    return
end

function run_strategies(config::sb.Config, dt::Number=0, set_type::Symbol=:default, moved_object::Union{sb.Object{<:Number}, Nothing}=nothing)
    calc_myPlayers(config, dt, set_type, moved_object)
    calc_opponend(config, dt, set_type, moved_object)
    return
end

end


# Not used from v0.3.0 on (PowerModels v0.15.3) - Kept for sentimental reasons
function get_solution_acdc(pm::_PM.AbstractPowerModel, sol::Dict{String,Any})
    _PM.add_setpoint_bus_voltage!(sol, pm)
    _PM.add_setpoint_generator_power!(sol, pm)
    _PM.add_setpoint_branch_flow!(sol, pm)
    _PM.add_setpoint_dcline_flow!(sol, pm)
    if haskey(pm.data, "convdc")
        add_dc_bus_voltage_setpoint(sol, pm)
        add_dcconverter_setpoint(sol, pm)
        add_dcgrid_flow_setpoint(sol,pm)
        add_dcbranch_losses(sol, pm)
        add_dcconverter_losses(sol, pm)
        add_dcconverter_voltage_setpoint(sol, pm)
        add_dcconverter_firing_angle(sol, pm)
    end
    return sol
end

function add_dcconverter_setpoint(sol, pm::_PM.AbstractPowerModel)
    _PM.add_setpoint!(sol, pm, "convdc", "pgrid", :pconv_tf_fr)
    _PM.add_setpoint!(sol, pm, "convdc", "qgrid", :qconv_tf_fr)
    _PM.add_setpoint!(sol, pm, "convdc", "pconv", :pconv_ac)
    _PM.add_setpoint!(sol, pm, "convdc", "qconv", :qconv_ac)
    _PM.add_setpoint!(sol, pm, "convdc", "pdc", :pconv_dc)
    _PM.add_setpoint!(sol, pm, "convdc", "iconv", :iconv_ac)
    _PM.add_setpoint!(sol, pm, "convdc", "ptf_fr", :pconv_tf_fr)
    _PM.add_setpoint!(sol, pm, "convdc", "ptf_to", :pconv_tf_to)
    add_dcconverter_voltage_setpoint(sol, pm)
end

function add_dcgrid_flow_setpoint(sol, pm::_PM.AbstractPowerModel)
    # check the branch flows were requested
    _PM.add_setpoint!(sol, pm, "branchdc", "pf", :p_dcgrid,  status_name="status",  var_key = (idx,item) -> (idx, item["fbusdc"], item["tbusdc"]))
    _PM.add_setpoint!(sol, pm, "branchdc", "pt", :p_dcgrid;  status_name="status",  var_key = (idx,item) -> (idx, item["tbusdc"], item["fbusdc"]))
end

function add_dcbranch_losses(sol, pm::_PM.AbstractPowerModel)
    for (i, branchdc) in sol["branchdc"]
        pf = branchdc["pf"]
        pt = branchdc["pt"]
        sol["branchdc"]["$i"]["ploss"] = pf + pt
    end
end

function add_dcconverter_losses(sol, pm::_PM.AbstractPowerModel)
    for (i, convdc) in sol["convdc"]
        pf = convdc["pdc"]
        pt = convdc["pgrid"]
        ptf_fr = convdc["ptf_fr"]
        ptf_to = convdc["ptf_to"]
        sol["convdc"]["$i"]["ploss_tot"] = pf + pt
        sol["convdc"]["$i"]["ploss_tf"] = ptf_fr + ptf_to
        sol["convdc"]["$i"]["ploss_conv"] = sol["convdc"]["$i"]["ploss_tot"] - sol["convdc"]["$i"]["ploss_tf"] # TODO update later with filter etc..
    end
end

function add_dc_bus_voltage_setpoint(sol, pm::_PM.AbstractPowerModel)
    _PM.add_setpoint!(sol, pm, "busdc", "vm", :vdcm, status_name="Vdc", inactive_status_value = 4)
end

function add_dcconverter_voltage_setpoint(sol, pm::_PM.AbstractPowerModel)
    _PM.add_setpoint!(sol, pm, "convdc", "vmconv", :vmc, status_name="islcc", inactive_status_value = 0)
    _PM.add_setpoint!(sol, pm, "convdc", "vaconv", :vac, status_name="islcc", inactive_status_value = 0)
    _PM.add_setpoint!(sol, pm, "convdc", "vmfilt", :vmf, status_name="islcc", inactive_status_value = 0)
    _PM.add_setpoint!(sol, pm, "convdc", "vafilt", :vaf, status_name="islcc", inactive_status_value = 0)
end

function add_dcconverter_firing_angle(sol, pm::_PM.AbstractPowerModel)
    _PM.add_setpoint!(sol, pm, "convdc", "phi", :phiconv, status_name="islcc", inactive_status_value = 0)
end

function get_solution_acdc_ne(pm::_PM.AbstractPowerModel, sol::Dict{String,Any})
    _PM.add_setpoint_bus_voltage!(sol, pm)
    _PM.add_setpoint_generator_power!(sol, pm)
    _PM.add_setpoint_branch_flow!(sol, pm)
    _PM.add_setpoint_dcline_flow!(sol, pm)
    if haskey(pm.data, "convdc")
        PowerModelsACDC.add_dc_bus_voltage_setpoint(sol, pm)
        PowerModelsACDC.add_dcconverter_setpoint(sol, pm)
        PowerModelsACDC.add_dcgrid_flow_setpoint(sol,pm)
        PowerModelsACDC.add_dcbranch_losses(sol, pm)
        PowerModelsACDC.add_dcconverter_losses(sol, pm)
        PowerModelsACDC.add_dcconverter_voltage_setpoint(sol, pm)
        PowerModelsACDC.add_dcconverter_firing_angle(sol, pm)
    elseif haskey(pm.data, "nw")
        if haskey(pm.data["nw"]["1"], "convdc")
            PowerModelsACDC.add_dc_bus_voltage_setpoint(sol, pm)
            PowerModelsACDC.add_dcconverter_setpoint(sol, pm)
            PowerModelsACDC.add_dcgrid_flow_setpoint(sol,pm)
            PowerModelsACDC.add_dcbranch_losses(sol, pm)
            PowerModelsACDC.add_dcconverter_losses(sol, pm)
            PowerModelsACDC.add_dcconverter_voltage_setpoint(sol, pm)
            PowerModelsACDC.add_dcconverter_firing_angle(sol, pm)
        end
    end
    if haskey(pm.data, "convdc_ne")
        add_dc_bus_voltage_setpoint_ne(sol, pm)
        add_dcconverter_setpoint_ne(sol, pm)
        add_dcgrid_flow_setpoint_ne(sol,pm)
        add_dcbranch_losses_ne(sol, pm)
        add_dcconverter_losses_ne(sol, pm)
    elseif haskey(pm.data, "nw")
        if haskey(pm.data["nw"]["1"], "convdc_ne")
            add_dc_bus_voltage_setpoint_ne(sol, pm)
            add_dcconverter_setpoint_ne(sol, pm)
            add_dcgrid_flow_setpoint_ne(sol,pm)
            add_dcbranch_losses_ne(sol, pm)
            add_dcconverter_losses_ne(sol, pm)
        end
    end
    return sol
end

function add_dc_bus_voltage_setpoint_ne(sol, pm::_PM.AbstractPowerModel)
    _PM.add_setpoint!(sol, pm, "busdc_ne", "vm", :vdcm_ne; status_name = "Cdc", inactive_status_value = 5)
end

function add_dcconverter_setpoint_ne(sol, pm::_PM.AbstractPowerModel)
    _PM.add_setpoint!(sol, pm, "convdc_ne", "pgrid", :pconv_tf_fr_ne)
    _PM.add_setpoint!(sol, pm, "convdc_ne", "qgrid", :qconv_tf_fr_ne)
    _PM.add_setpoint!(sol, pm, "convdc_ne", "pconv", :pconv_ac_ne)
    _PM.add_setpoint!(sol, pm, "convdc_ne", "qconv", :qconv_ac_ne)
    _PM.add_setpoint!(sol, pm, "convdc_ne", "pdc", :pconv_dc_ne)
    _PM.add_setpoint!(sol, pm, "convdc_ne", "iconv", :iconv_ac_ne)
    _PM.add_setpoint!(sol, pm, "convdc_ne", "ireactor", :irc_sq_ne)
    _PM.add_setpoint!(sol, pm, "convdc_ne", "iconv_sq", :iconv_ac_sq_ne)
    _PM.add_setpoint!(sol, pm, "convdc_ne", "ppr_fr", :pconv_pr_fr_ne)
    _PM.add_setpoint!(sol, pm, "convdc_ne", "ptf_to", :pconv_tf_to_ne)
    _PM.add_setpoint!(sol, pm, "convdc_ne", "isbuilt", :conv_ne)
    _PM.add_setpoint!(sol, pm, "convdc_ne", "itr", :itf_sq_ne)
    _PM.add_setpoint!(sol, pm, "convdc_ne", "ipr", :irc_sq_ne)
    _PM.add_setpoint!(sol, pm, "convdc_ne", "pfilt_to", :pconv_tf_to_ne)
    _PM.add_setpoint!(sol, pm, "convdc_ne", "pfilt_fr", :pconv_pr_fr_ne)

    add_dcconverter_voltage_setpoint_ne(sol, pm)
    add_dcconverter_firing_angle_ne(sol, pm)
end

function add_dcconverter_voltage_setpoint_ne(sol, pm::_PM.AbstractPowerModel)
    _PM.add_setpoint!(sol, pm, "convdc_ne", "vmconv", :vmc_ne)
    _PM.add_setpoint!(sol, pm, "convdc_ne", "vaconv", :vac_ne)
    _PM.add_setpoint!(sol, pm, "convdc_ne", "vmfilt", :vmf_ne)
    _PM.add_setpoint!(sol, pm, "convdc_ne", "vafilt", :vaf_ne)
    _PM.add_setpoint!(sol, pm, "convdc_ne", "wf", :wf_ac_ne)
    _PM.add_setpoint!(sol, pm, "convdc_ne", "w_du", :w_du)
    _PM.add_setpoint!(sol, pm, "convdc_ne", "wc", :wc_ac_ne)
end

function add_dcgrid_flow_setpoint_ne(sol, pm::_PM.AbstractPowerModel)
    # check the branch flows were requested
    _PM.add_setpoint!(sol, pm, "branchdc_ne", "pf", :p_dcgrid_ne; status_name="status",  var_key = (idx,item) -> (idx, item["fbusdc"], item["tbusdc"]))
    _PM.add_setpoint!(sol, pm, "branchdc_ne", "pt", :p_dcgrid_ne; status_name="status",  var_key = (idx,item) -> (idx, item["tbusdc"], item["fbusdc"]))
    _PM.add_setpoint!(sol, pm, "branchdc_ne", "isbuilt", :branchdc_ne)
    _PM.add_setpoint!(sol, pm, "branchdc_ne", "wdc_to", :wdc_du_to; status_name="status")
end

function add_dcbranch_losses_ne(sol, pm::_PM.AbstractPowerModel)
    for (i, branchdc) in sol["branchdc_ne"]
        pf = branchdc["pf"]
        pt = branchdc["pt"]
        sol["branchdc_ne"]["$i"]["ploss"] = pf + pt
    end
end

function add_dcconverter_losses_ne(sol, pm::_PM.AbstractPowerModel)
    for (i, convdc) in sol["convdc_ne"]
        pdc = convdc["pdc"]
        pgrid = convdc["pgrid"]
        ptf_to = convdc["ptf_to"]
        pconv = convdc["pconv"]
        sol["convdc_ne"]["$i"]["ploss_tot"] = pdc - pgrid
        sol["convdc_ne"]["$i"]["ploss_tf"] = pgrid + ptf_to
        sol["convdc_ne"]["$i"]["ploss_re"] = - ptf_to + pconv # ptf_to + ppr_fr = 0
        sol["convdc_ne"]["$i"]["ploss_conv"] =  pconv + pdc
    end
end

function add_dcconverter_firing_angle_ne(sol, pm::_PM.AbstractPowerModel)
    _PM.add_setpoint!(sol, pm, "convdc_ne", "phi", :phiconv_ne)
end


module CSDP

using LinearAlgebra # For Diagonal
using SparseArrays # For SparseMatrixCSC

# Try to load the binary dependency
if isfile(joinpath(dirname(@__FILE__),"..","deps","deps.jl"))
    include("../deps/deps.jl")
else
    error("CSDP not properly installed. Please run Pkg.build(\"CSDP\")")
end

export Blockmatrix

include("blockdiag.jl")
include("blockmat.h.jl")
include("blockmat.jl")
include("declarations.h.jl")
include("declarations.jl")
include("debug-mat.jl")
include("options.jl")
include("MOI_wrapper.jl")
include("MPB_wrapper.jl")

end # module


_ft(id, day) = isodd(id + day)
function run!(::Experiment{:Amodio20_exp1}, models)
    results = DataFrame(id = Int[], action = String[], foodtype = String[], A = Int[], B = Int[], C = Int[])
    for id in eachindex(models)
        m = models[id]
        trays = [Tray(i) for i in 1:3] # A, B, C
        for day in 1:9
            remove!(m, Any)
            wait!(m, 12u"hr")
            add!(m, trays[_abc(id, day)])
            if _ft(id, day)
                add!(m, PowderedKibble, 50)
            else
                add!(m, PowderedPeanut, 50)
            end
            wait!(m, 15u"minute")
            remove!(m, Any)
            add!(m, MaintenanceDiet)
            if day == 9
                wait!(m, 3u"hr")
                remove!(m, Any)
                add!(m, trays)
                add!(m, Kibble, 25)
                add!(m, Peanut, 25)
                wait!(m, 15u"minute")
                remove!(m, Any)
                push!(results, [id; "cache"; "peanut"; countcache.(trays, Peanut)])
                push!(results, [id; "cache"; "kibble"; countcache.(trays, Kibble)])
            else
                wait!(m, 3u"hr")
                remove!(m, Any)
                add!(m, trays)
                wait!(m, 15u"minute")
                add!(m, MaintenanceDiet)
                wait!(m, 8.5u"hr")
            end
        end
    end
    results
end

# 1. Rome 2. Quito 3. Lisbon 4. Caracas 5. Wellington 6. Washington
_nf(id, day) = isodd(id + day)
_abc(id, day) = (day + div(id - 1, 2) - 1) % 3 + 1
function run!(::Experiment{:Amodio20_exp2}, models)
    results = DataFrame(id = Int[], action = String[], A = Int[], B = Int[], C = Int[])
    for id in eachindex(models)
        m = models[id]
        trays = [Tray(i) for i in 1:3] # A, B, C
        for day in 1:9
            remove!(m, Any)
            wait!(m, 12u"hr")
            add!(m, trays[_abc(id, day)])
            if _nf(id, day)
                add!(m, PowderedKibble, 50)
            end
            wait!(m, 15u"minute")
            remove!(m, Any)
            add!(m, MaintenanceDiet)
            if day == 9
                wait!(m, 3u"hr")
                remove!(m, Any)
                add!(m, trays)
                add!(m, Kibble, 50)
                wait!(m, 15u"minute")
                remove!(m, Any)
                push!(results, [id; "cache"; countcache.(trays)])
            else
                wait!(m, 3u"hr")
                remove!(m, Any)
                add!(m, trays)
                wait!(m, 15u"minute")
                add!(m, MaintenanceDiet)
                wait!(m, 8.5u"hr")
            end
        end
    end
    results
end


module TypeInstantiationTests

using Interpolations, Test

# NO DIMSPECS
# tests that we forward types correctly to the instance constructors
const A1 = rand(15)
const A2 = rand(15, 10)

# B-splines
for GT in (OnCell, OnGrid)
    # ...without boundary conditions
    for D in (Constant, Linear)
        @inferred(interpolate(A1, BSpline(D), GT))
        @inferred(interpolate(A1, BSpline(D), GT()))
        @inferred(interpolate(A1, BSpline(D()), GT))
        @inferred(interpolate(A2, BSpline(D), GT))
        @inferred(interpolate(A2, BSpline(D), GT()))
        @inferred(interpolate(A2, BSpline(D()), GT))

        @inferred(interpolate!(copy(A1), BSpline(D), GT))
        @inferred(interpolate!(copy(A1), BSpline(D), GT()))
        @inferred(interpolate!(copy(A1), BSpline(D()), GT))
        @inferred(interpolate!(copy(A2), BSpline(D), GT))
        @inferred(interpolate!(copy(A2), BSpline(D), GT()))
        @inferred(interpolate!(copy(A2), BSpline(D()), GT))
    end

    # Quadratic
    for BC in (Flat, Line, Periodic, Reflect, Free)
        if BC != InPlace
            @inferred(interpolate(A1, BSpline(Quadratic(BC)), GT))
            @inferred(interpolate(A1, BSpline(Quadratic(BC())), GT))
            @inferred(interpolate(A1, BSpline(Quadratic(BC)), GT()))
            @inferred(interpolate(A2, BSpline(Quadratic(BC)), GT))
            @inferred(interpolate(A2, BSpline(Quadratic(BC())), GT))
            @inferred(interpolate(A2, BSpline(Quadratic(BC)), GT()))
        end

        @inferred(interpolate!(copy(A1), BSpline(Quadratic(BC)), GT))
        @inferred(interpolate!(copy(A1), BSpline(Quadratic(BC())), GT))
        @inferred(interpolate!(copy(A1), BSpline(Quadratic(BC)), GT()))
        @inferred(interpolate!(copy(A2), BSpline(Quadratic(BC)), GT))
        @inferred(interpolate!(copy(A2), BSpline(Quadratic(BC())), GT))
        @inferred(interpolate!(copy(A2), BSpline(Quadratic(BC)), GT()))
    end
end

# Gridded
const knots1 = (sort(rand(15)),)
const knots2 = (sort(rand(15)), sort(rand(10)))
for D in (Constant, Linear)
    @inferred(interpolate(knots1, A1, Gridded(D)))
    @inferred(interpolate(knots2, A2, Gridded(D)))

    @inferred(interpolate!(knots1, copy(A1), Gridded(D)))
    @inferred(interpolate!(knots2, copy(A2), Gridded(D)))
end

# DIMSPECS

# test that constructing dimspecs work
for T in (
    Tuple{OnGrid,OnCell},
    Tuple{BSpline{Linear},BSpline{Quadratic{Free}}}
    )
    @test isa(@inferred(Interpolations.construct_instance(T)), T)
end
# sample one dimspec for each interpolation constructor to see that it
# calls the construct_instance() function correctly
@inferred(interpolate(A2, Tuple{BSpline{Linear},BSpline{Constant}}, Tuple{OnGrid,OnCell}))
@inferred(interpolate!(copy(A2), Tuple{BSpline{Linear},BSpline{Constant}}, Tuple{OnGrid,OnCell}))
@inferred(interpolate(A2, Tuple{BSpline{Linear},BSpline{Constant}}, (OnGrid(),OnCell())))
@inferred(interpolate!(copy(A2), Tuple{BSpline{Linear},BSpline{Constant}}, (OnGrid(),OnCell())))
@inferred(interpolate(A2, (BSpline(Linear()),BSpline(Constant())), Tuple{OnGrid,OnCell}))
@inferred(interpolate!(copy(A2), (BSpline(Linear()),BSpline(Constant())), Tuple{OnGrid,OnCell}))

end


using .Networks

using JuMP
using Dates
using DataStructures
using Printf
using Parameters

"""
REF_SCHEDULE_TYPE : Indicates wether to consider the preceding market or TSO schedule as a reference.
                    The reference schedule is used to get decided commitment and production levels if
                      tso actions are missing.
"""
@with_kw mutable struct EnergyMarketConfigs
    loss_of_load_penalty = 1e7
    out_path = nothing
    problem_name = "EnergyMarket"
    REF_SCHEDULE_TYPE::Union{Market,TSO} = TSO();
    CONSIDER_TSOACTIONS_LIMITATIONS::Bool = false
    CONSIDER_TSOACTIONS_IMPOSITIONS::Bool = false
    CONSIDER_GRATIS_STARTS::Bool = true
end

@with_kw struct EnergyMarketLimitableModel <: AbstractLimitableModel
    #ts,s
    p_global_capping = SortedDict{Tuple{DateTime,String},VariableRef}();
    #firmness_constraints
end

@with_kw struct EnergyMarketPilotableModel <: AbstractPilotableModel
    #gen,ts,s
    p_injected = SortedDict{Tuple{String,DateTime,String},VariableRef}();
    #gen,ts,s
    b_start = SortedDict{Tuple{String,DateTime,String},VariableRef}();
    #gen,ts,s
    b_on = SortedDict{Tuple{String,DateTime,String},VariableRef}();
    #commitment_constraints = Dict{Tuple{String,DateTime,String},ConstraintRef}();
    #firmness_constraints
end

@with_kw struct EnergyMarketLoLModel <: AbstractLoLModel
    #ts,s
    p_global_loss_of_load = SortedDict{Tuple{DateTime,String},VariableRef}();
end

@with_kw mutable struct EnergyMarketObjectiveModel <: AbstractObjectiveModel
    prop_cost = AffExpr(0)
    start_cost = AffExpr(0)
    penalty = AffExpr(0)

    full_obj = AffExpr(0)
end

@with_kw mutable struct EnergyMarketModel <: AbstractModelContainer
    model::Model = Model()
    limitable_model::EnergyMarketLimitableModel = EnergyMarketLimitableModel()
    pilotable_model::EnergyMarketPilotableModel = EnergyMarketPilotableModel()
    lol_model::EnergyMarketLoLModel = EnergyMarketLoLModel()
    objective_model::EnergyMarketObjectiveModel = EnergyMarketObjectiveModel()
    eod_constraint::SortedDict{Tuple{Dates.DateTime,String}, ConstraintRef} =
        SortedDict{Tuple{Dates.DateTime,String}, ConstraintRef}()
end

SCENARIOS_DELIMITER = "_+_"

function aggregate_scenario_name(scenarios::Vector{String})
    return join(scenarios, SCENARIOS_DELIMITER)
end
function aggregate_scenario_name(context::AbstractContext, ech::Dates.DateTime)
    scenarios = get_scenarios(context, ech)
    return aggregate_scenario_name(scenarios)
end

function has_positive_slack(model_container::EnergyMarketModel)::Bool
    return has_positive_value(model_container.lol_model.p_global_loss_of_load)
end

"""
    energy_market
# Arguments
    - `network::Networks.Network`
    - `target_timepoints::Vector{Dates.DateTime}`
    - `generators_initial_state::SortedDict{String,GeneratorState}` :
        The ON/OFF state of each generator just before the first target timepoint.
    - `scenarios::Vector{String}` : considered scenarios
    - `uncertainties_at_ech::UncertaintiesAtEch` :
        The considered limitables injections and bus consumption realisations per scenario
         for the current timepoint of execution (i.e. `ech`).
    - `firmness::Firmness` : The required level of firmness for commitment and power level decisions
    - `preceding_market_schedule::Schedule` : The preceding market schedule may be used to set already decided values.
    - `preceding_tso_schedule::Schedule` : The preceding tso schedule may be used to set already decided values.
    - `tso_actions::TSOActions` : The preceding tso actions maybe used for additional constraints.
        The behaviour of the model wit hrespect to the TSO actions is defined by the parameters
         `CONSIDER_TSOACTIONS_LIMITATIONS` and `CONSIDER_TSOACTIONS_IMPOSITIONS`
         of `configs::EnergyMarketConfigs`
    - `gratis_starts::Set{Tuple{String,Dates.DateTime}}` :
        Tuples of (gen_id, ts) giving already paid for starting decisions. So if the market starts
         unit gen_id at timestep ts, it will not pay the starting cost.
        These are ignored if EnergyMarketConfigs::CONSIDER_GRATIS_STARTS is false
    - `configs::EnergyMarketConfigs` :
        settings modifying the behaviour of the model.
"""
function energy_market(network::Networks.Network,
                    target_timepoints::Vector{Dates.DateTime},
                    generators_initial_state::SortedDict{String,GeneratorState},
                    scenarios::Vector{String},
                    uncertainties_at_ech::UncertaintiesAtEch,
                    firmness::Firmness,
                    preceding_market_schedule::Schedule,
                    preceding_tso_schedule::Schedule,
                    tso_actions::TSOActions,
                    gratis_starts::Set{Tuple{String,Dates.DateTime}},
                    configs::EnergyMarketConfigs
                    )
    #if gratis_starts is not empty, we should CONSIDER_GRATIS_STARTS
    @assert( configs.CONSIDER_GRATIS_STARTS | isempty(gratis_starts) )
    #if not there a risk of cutting conso instead of using a generator
    @assert all(configs.loss_of_load_penalty > Networks.get_prop_cost(gen)
                for gen in Networks.get_generators(network))

    if is_market(configs.REF_SCHEDULE_TYPE)
        reference_schedule = preceding_market_schedule
    elseif is_tso(configs.REF_SCHEDULE_TYPE)
        reference_schedule = preceding_tso_schedule
    else
        throw( error("Invalid REF_SCHEDULE_TYPE config.") )
    end

    pilotables_list_l = Networks.get_generators_of_type(network, Networks.PILOTABLE)
    limitables_list_l = Networks.get_generators_of_type(network, Networks.LIMITABLE)
    buses_list = Networks.get_buses(network)

    model_container_l = EnergyMarketModel()

    # Variables
    add_pilotables_vars!(model_container_l,
                        pilotables_list_l, target_timepoints, scenarios,
                        injection_vars=true, commitment_vars=true)
    add_limitables_vars!(model_container_l, target_timepoints, scenarios,
                        global_capping_vars=true
                        )
    add_lol_vars!(model_container_l, target_timepoints, scenarios,
                global_lol_vars=true)

    # Constraints

    # Pilotables
    pilotable_power_constraints!(model_container_l.model,
                                model_container_l.pilotable_model, pilotables_list_l, target_timepoints, scenarios,
                                firmness, reference_schedule,
                                always_link_scenarios=false)
    unit_commitment_constraints!(model_container_l.model,
                                model_container_l.pilotable_model, pilotables_list_l,  target_timepoints, scenarios,
                                firmness, reference_schedule, generators_initial_state,
                                always_link_scenarios=false)
    if configs.CONSIDER_TSOACTIONS_IMPOSITIONS
        respect_impositions_constraints!(model_container_l.model,
                                        model_container_l.pilotable_model, pilotables_list_l,  target_timepoints, scenarios,
                                        tso_actions)
    end

    # Limitables
    if configs.CONSIDER_TSOACTIONS_LIMITATIONS
        global_capping_constraints!(model_container_l.model,
                                    model_container_l.limitable_model,limitables_list_l, target_timepoints, scenarios,
                                    uncertainties_at_ech, tso_actions=tso_actions)
    else
        global_capping_constraints!(model_container_l.model,
                                    model_container_l.limitable_model, limitables_list_l, target_timepoints, scenarios,
                                    uncertainties_at_ech)
    end

    # LoL
    global_lol_constraints!(model_container_l.model,
                            model_container_l.lol_model, buses_list, target_timepoints, scenarios,
                            uncertainties_at_ech)

    # EOD
    eod_constraints!(model_container_l.model, model_container_l.eod_constraint,
                        model_container_l.pilotable_model,
                        model_container_l.limitable_model,
                        model_container_l.lol_model,
                        target_timepoints, scenarios,
                        uncertainties_at_ech, network)

    add_objective!(model_container_l, network, gratis_starts, configs.loss_of_load_penalty)

    solve!(model_container_l, configs.problem_name, configs.out_path)

    return model_container_l
end

function add_objective!(model_container::EnergyMarketModel, network, gratis_starts, loss_of_load_cost)
    # cost for starting pilotables
    add_pilotable_start_cost!(model_container.objective_model.start_cost,
                            model_container.pilotable_model.b_start, network, gratis_starts)

    # No limitable cost

    # cost for using pilotables
    add_prop_cost!(model_container.objective_model.prop_cost,
                    model_container.pilotable_model.p_injected, network)

    # cost for cutting load/consumption
    add_coeffxsum_cost!(model_container.objective_model.penalty,
                        model_container.lol_model.p_global_loss_of_load, loss_of_load_cost)

    model_container.objective_model.full_obj = ( model_container.objective_model.start_cost +
                                                model_container.objective_model.prop_cost +
                                                model_container.objective_model.penalty )
    @objective(model_container.model, Min, model_container.objective_model.full_obj)
    return model_container
end

###########################
# Context-update
###########################

function update_schedule_capping!(market_schedule, context, ech, limitable_model::EnergyMarketLimitableModel, consider_limitations::Bool)
    reset_capping!(market_schedule)
    uncertainties = get_uncertainties(context, ech)

    for ((ts, scenario), p_capping_var) in limitable_model.p_global_capping
        capped_power = value(p_capping_var)
        for s in split_str(scenario, SCENARIOS_DELIMITER, keepempty=false)
        #for EnergyMarket, s==scenario
        #for EnergyMarketAtFO, this allows handling aggregate scenarios "S1_+_S2"
            limitables_ids = Networks.get_id.(Networks.get_generators_of_type(get_network(context), Networks.LIMITABLE))

            if capped_power > 1e-09
            #distribute the capped power on limitables
                limitations = consider_limitations ? get_limitations(get_tso_actions(context)) : SortedDict{Tuple{String, Dates.DateTime}, Float64}()
                capped_by_limitations = compute_capped(uncertainties, limitations, get_network(context), ts, s)
                capped_by_eod = capped_power - capped_by_limitations

                @printf("capped %f power in scenario %s at ts %s\n", capped_power, s, ts)
                @printf("capped %f power for eod reasons in scenario %s at ts %s\n", capped_by_eod, s, ts)
                distribution_key = Dict{String,Float64}(gen_id_l => get_capacity(gen_id_l, ts, s,
                                                                                limitations,
                                                                                uncertainties)
                                                        for gen_id_l in limitables_ids)
                distribution_key = normalize_values(distribution_key)
                @debug(@sprintf("distribution_key : %s", distribution_key))

                for (gen_id, coeff) in distribution_key
                    capped_value = ( get_capped_by_limitations(gen_id, ts, s, limitations, uncertainties)
                                    + coeff * capped_by_eod )
                    market_schedule.capping[gen_id, ts, s] = capped_value
                end
            else
                for gen_id in limitables_ids
                    market_schedule.capping[gen_id, ts, s] = 0.
                end
            end
        end
    end
end

function update_schedule_loss_of_load!(market_schedule, context, ech, lol_model::EnergyMarketLoLModel)
    reset_loss_of_load_by_bus!(market_schedule)
    uncertainties = get_uncertainties(context, ech)

    for ((ts, scenario), p_loss_of_load_var) in lol_model.p_global_loss_of_load
        total_loss_of_load = value(p_loss_of_load_var)
        for s in split_str(scenario, SCENARIOS_DELIMITER, keepempty=false)
        #for EnergyMarket, s==scenario
        #for EnergyMarketAtFO, this allows handling aggregate scenarios "S1_+_S2"
            bus_ids = Networks.get_id.(Networks.get_buses(get_network(context)))

            if total_loss_of_load > 1e-09
            #distribute the cut conso on buses
                @printf("cut conso %f in scenario %s at ts %s\n", total_loss_of_load, s, ts)
                distribution_key = Dict{String,Float64}(bus_id_l => get_uncertainties(uncertainties, bus_id_l, ts, s)
                                                        for bus_id_l in bus_ids)
                distribution_key = normalize_values(distribution_key)
                @debug(@sprintf("distribution_key : %s", distribution_key))

                for (bus_id, coeff) in distribution_key
                    cut_load_on_bus = coeff * total_loss_of_load
                    market_schedule.loss_of_load_by_bus[bus_id, ts, s] = cut_load_on_bus
                end
            else
                for bus_id in bus_ids
                    market_schedule.loss_of_load_by_bus[bus_id, ts, s] = 0.
                end
            end
        end
    end
end

function get_capacity(gen_id, ts, s, limitations, uncertainties_at_ech)
    p_lim = get_limitation(limitations, gen_id, ts)

    if ismissing(p_lim)
        return get_uncertainties(uncertainties_at_ech, gen_id, ts, s)
    else
        return min(p_lim, get_uncertainties(uncertainties_at_ech, gen_id, ts, s))
    end
end

function get_capped_by_limitations(gen_id, ts, s, limitations, uncertainties_at_ech)
    p_lim = get_limitation(limitations, gen_id, ts)

    gen_capped = 0.
    if !ismissing(p_lim) && get_uncertainties(uncertainties_at_ech, gen_id, ts, s) > p_lim
        gen_capped = ( get_uncertainties(uncertainties_at_ech, gen_id, ts, s) - p_lim )
        @debug(@sprintf("%s capped %f : limit is %f out of %f",
                        gen_id, gen_capped, p_lim, get_uncertainties(uncertainties_at_ech, gen_id, ts, s)))
    end
    return gen_capped
end

function compute_capped(uncertainties_at_ech::UncertaintiesAtEch,
                        limitations::SortedDict{Tuple{String, Dates.DateTime}, Float64},
                        limitable_generators, ts, s)
    if isempty(limitations)
        return 0.
    end

    capped = 0.
    for limitable_gen in limitable_generators
        gen_id = Networks.get_id(limitable_gen)
        capped += get_capped_by_limitations(gen_id, ts, s, limitations, uncertainties_at_ech)
    end
    return capped
end
function compute_capped(uncertainties_at_ech::UncertaintiesAtEch,
                        limitations::SortedDict{Tuple{String, Dates.DateTime}, Float64},
                        network::Networks.Network, ts, s)
    limitable_generators = Networks.get_generators_of_type(network, Networks.LIMITABLE)
    return compute_capped(uncertainties_at_ech, limitations, limitable_generators, ts, s)
end


function get_a(obj::OGMFile)
    return jcall(obj, "getA", jdouble, ())
end

function get_anomaly(obj::OGMFile)
    return jcall(obj, "getAnomaly", jdouble, ())
end

function get_anomaly_type(obj::OGMFile)
    return jcall(obj, "getAnomalyType", PositionAngle, ())
end

function get_cov_ref_frame(obj::OGMFile)
    return jcall(obj, "getCovRefFrame", Frame, ())
end

function get_cov_ref_lof_type(obj::OGMFile)
    return jcall(obj, "getCovRefLofType", LOFType, ())
end

function get_covariance_comment(obj::OGMFile)
    return jcall(obj, "getCovarianceComment", List, ())
end

function get_covariance_matrix(obj::OGMFile)
    return jcall(obj, "getCovarianceMatrix", RealMatrix, ())
end

function get_drag_area(obj::OGMFile)
    return jcall(obj, "getDragArea", jdouble, ())
end

function get_drag_coeff(obj::OGMFile)
    return jcall(obj, "getDragCoeff", jdouble, ())
end

function get_e(obj::OGMFile)
    return jcall(obj, "getE", jdouble, ())
end

function get_epoch(obj::OGMFile)
    return jcall(obj, "getEpoch", AbsoluteDate, ())
end

function get_epoch_comment(obj::OGMFile)
    return jcall(obj, "getEpochComment", List, ())
end

function get_i(obj::OGMFile)
    return jcall(obj, "getI", jdouble, ())
end

function get_keplerian_elements_comment(obj::OGMFile)
    return jcall(obj, "getKeplerianElementsComment", List, ())
end

function get_mass(obj::OGMFile)
    return jcall(obj, "getMass", jdouble, ())
end

function get_meta_data(obj::OGMFile)
    return jcall(obj, "getMetaData", ODMMetaData, ())
end

function get_pa(obj::OGMFile)
    return jcall(obj, "getPa", jdouble, ())
end

function get_raan(obj::OGMFile)
    return jcall(obj, "getRaan", jdouble, ())
end

function get_solar_rad_area(obj::OGMFile)
    return jcall(obj, "getSolarRadArea", jdouble, ())
end

function get_solar_rad_coeff(obj::OGMFile)
    return jcall(obj, "getSolarRadCoeff", jdouble, ())
end

function get_spacecraft_comment(obj::OGMFile)
    return jcall(obj, "getSpacecraftComment", List, ())
end

function get_user_defined_parameters(obj::OGMFile)
    return jcall(obj, "getUserDefinedParameters", Map, ())
end

function has_covariance_matrix(obj::OGMFile)
    return jcall(obj, "hasCovarianceMatrix", jboolean, ())
end

function has_keplerian_elements(obj::OGMFile)
    return jcall(obj, "hasKeplerianElements", jboolean, ())
end


using Oceananigans.Grids: Flat, Bounded, y_domain
using Oceananigans.TurbulenceClosures: AbstractTurbulenceClosure
using Oceananigans.TurbulenceClosures.CATKEVerticalDiffusivities: _top_tke_flux, CATKEVDArray

import Oceananigans.Grids: validate_size, validate_halo, HRegRectilinearGrid
import Oceananigans.TurbulenceClosures: time_discretization, calculate_diffusivities!, with_tracers
import Oceananigans.TurbulenceClosures: ∂ⱼ_τ₁ⱼ, ∂ⱼ_τ₂ⱼ, ∂ⱼ_τ₃ⱼ, ∇_dot_qᶜ
import Oceananigans.TurbulenceClosures.CATKEVerticalDiffusivities: top_tke_flux
import Oceananigans.Coriolis: x_f_cross_U, y_f_cross_U, z_f_cross_U

#####
##### Implements a "single column model mode" for HydrostaticFreeSurfaceModel
#####

const YZSliceGrid = Union{AbstractGrid{<:AbstractFloat, <:Flat, <:Bounded, <:Bounded},
                          AbstractGrid{<:AbstractFloat, <:Flat, <:Periodic, <:Bounded}}

@inline function ∂ⱼ_τ₁ⱼ(i, j, k, grid::YZSliceGrid, closure_array::ClosureArray, args...)
    @inbounds closure = closure_array[i]
    return ∂ⱼ_τ₁ⱼ(i, j, k, grid, closure, args...)
end

@inline function ∂ⱼ_τ₂ⱼ(i, j, k, grid::YZSliceGrid, closure_array::ClosureArray, args...)
    @inbounds closure = closure_array[i]
    return ∂ⱼ_τ₂ⱼ(i, j, k, grid, closure, args...)
end

@inline function ∇_dot_qᶜ(i, j, k, grid::YZSliceGrid, closure_array::ClosureArray, c, tracer_index, args...)
    @inbounds closure = closure_array[i]
    return ∇_dot_qᶜ(i, j, k, grid, closure, c, tracer_index, args...)
end

struct SliceEnsembleSize
    ensemble :: Int
    Ny :: Int
    Nz :: Int
    Hy :: Int
    Hz :: Int
end

SliceEnsembleSize(; size, ensemble=0, halo=(1, 1)) = SliceEnsembleSize(ensemble, size[1], size[2], halo[1], halo[2])

validate_size(TX, TY, TZ, e::SliceEnsembleSize) = tuple(e.ensemble, e.Ny, e.Nz)
validate_halo(TX, TY, TZ, e::SliceEnsembleSize) = tuple(0, e.Hy, e.Hz)

#####
##### CATKEVerticalDiffusivity helpers
#####

""" Compute the flux of TKE through the surface / top boundary. """
@inline function top_tke_flux(i, j, grid::YZSliceGrid, clock, fields, parameters, closure_array::CATKEVDArray, buoyancy)
    top_tracer_bcs = parameters.top_tracer_boundary_conditions
    top_velocity_bcs = parameters.top_velocity_boundary_conditions
    @inbounds closure = closure_array[i]

    return _top_tke_flux(i, j, grid, closure.surface_TKE_flux, closure,
                         buoyancy, fields, top_tracer_bcs, top_velocity_bcs, clock)
end

@inline function hydrostatic_turbulent_kinetic_energy_tendency(i, j, k, grid::YZSliceGrid,
                                                               val_tracer_index::Val{tracer_index},
                                                               advection,
                                                               closure_array::CATKEVDArray, args...) where tracer_index

    @inbounds closure = closure_array[i]
    return hydrostatic_turbulent_kinetic_energy_tendency(i, j, k, grid, val_tracer_index, advection, closure, args...)
end

#####
##### Arrays of Coriolises
#####

const CoriolisVector = AbstractVector{<:AbstractRotation}

@inline x_f_cross_U(i, j, k, grid::YZSliceGrid, coriolis::CoriolisVector, U) = @inbounds x_f_cross_U(i, j, k, grid, coriolis[i], U)
@inline y_f_cross_U(i, j, k, grid::YZSliceGrid, coriolis::CoriolisVector, U) = @inbounds y_f_cross_U(i, j, k, grid, coriolis[i], U)
@inline z_f_cross_U(i, j, k, grid::YZSliceGrid, coriolis::CoriolisVector, U) = @inbounds z_f_cross_U(i, j, k, grid, coriolis[i], U)

function FFTImplicitFreeSurfaceSolver(arch, grid::YZSliceGrid, settings)

    grid isa HRegRectilinearGrid ||
        throw(ArgumentError("FFTImplicitFreeSurfaceSolver requires horizontally-regular rectilinear grids."))

    # Construct a "horizontal grid". We support either x or y being Flat, but not both.
    TY = topology(grid, 2)

    sz = SliceEnsembleSize(size=(grid.Ny, 1), ensemble=grid.Nx, halo=(grid.Hy, 0))

    horizontal_grid = RectilinearGrid(; topology = (Flat, TY, Flat),
                                        size = sz,
                                        halo = grid.Hy,
                                        y = y_domain(grid))

    solver = FFTBasedPoissonSolver(arch, horizontal_grid)
    right_hand_side = solver.storage

    return FFTImplicitFreeSurfaceSolver(solver, grid, horizontal_grid, right_hand_side)
end


macro import_slider_huge()
    :(
        using ComplexVisual:
            CV_SliderContainer, cv_create_hslider, cv_setup_hslider,
            cv_anchor, CV_SliderCreateData
    )
end

"""
A (Container-)Canvas with a slider (a `CV_Math2DCanvas`) inside and its rulers.
"""
struct CV_SliderContainer{ccsT, dcbT} <: CV_2DContainer # {{{
    surface        :: Cairo.CairoSurfaceImage{UInt32}
    pixel_width    :: Int32
    pixel_height   :: Int32
    bounding_box   :: CV_Rectangle{Int32} # zero-based
    user_box       :: CV_Rectangle{Int32} # user-coordinates (result of layout)
                                          # typically nonzero-based
    can_slider     :: CV_Math2DCanvas
    can_slider_l   :: CV_2DLayoutPosition # postion of can_slider inside
                                          # container
    cc_can_slider  :: ccsT
    decorator_cb   :: dcbT
end

function cv_anchor(cont::CV_SliderContainer, name::Symbol)
    name_str = string(name)
    if startswith(name_str, "slider_")
        inner_sym = Symbol(name_str[8:end])
        return cv_local2global(cont, cont.can_slider_l,
            cv_anchor(cont.can_slider, inner_sym)...)
    else
        return cv_anchor(cont.bounding_box, name)
    end
end
# }}}

struct CV_SliderCreateData{ccsT, dcbT, contextT <: CV_2DCanvasContext
                            } <: CV_CreateData
    slider_container   :: CV_SliderContainer{ccsT, dcbT}
    container_context  :: contextT
    internal_layout    :: CV_2DLayout
end

function cv_create_hslider(pixel_width::Integer, pixel_height::Integer,
        value_min::Real, value_max::Real,
        rulers::NTuple{N, CV_Ruler};
        attach::CV_AttachType=cv_south,
        decoraction_with_layout_and_position_callback=nothing) where {N}  # {{{

    if pixel_width < 0  ||  pixel_height < 0
        cv_error("pixel_width and pixel_height must be postive")
    end
    if !isfinite(value_min)  ||  !isfinite(value_max)
        cv_error("value_min and value_max must be finite")
    end
    if !(value_min < value_max)
        cv_error("value_min must be smaller than value_max")
    end
    pixel_width, pixel_height = Int32(pixel_width), Int32(pixel_height)
    value_min, value_max = Float64(value_min), Float64(value_max)
    res = pixel_width / (value_max - value_min)
    math_height = pixel_height / res

    slider_layout = CV_2DLayout()
    can_slider = CV_Math2DCanvas(
        value_min + math_height*1im, value_max + 0.0*1im, res)
    can_slider_l = cv_add_canvas!(slider_layout, can_slider, (0,0), (0,0))
    cc_can_slider = cv_create_context(can_slider)

    decorator_cb = (decoraction_with_layout_and_position_callback !== nothing) ?
        decoraction_with_layout_and_position_callback(
            slider_layout, can_slider_l) : nothing

    if N > 0
        can_axis_l = cv_ticks_labels(slider_layout, can_slider_l, attach, rulers)
    end

    bb = slider_layout.seen_boxes.bounding_box
    slider_container = CV_SliderContainer(
        cv_create_cairo_image_surface(cv_width(bb), cv_height(bb)),
        cv_width(bb), cv_height(bb),
        CV_Rectangle(cv_height(bb), Int32(0), Int32(0), cv_width(bb)),
        bb, can_slider, can_slider_l, cc_can_slider, decorator_cb)

    cc_container = cv_create_context(slider_container; fill_with=cv_color(1, 1, 1))
    if N >0
        can_axis_l(cc_container)
    end

    return CV_SliderCreateData(slider_container, cc_container, slider_layout)
end # }}}

function cv_setup_hslider(setup::CV_SceneSetupChain,
        slider_data::CV_SliderCreateData,
        cont_l::CV_2DLayoutPosition,
        painter::CV_Painter, set_slider_value_func;
        react_to_actionpixel_update::Bool=true,
        react_to_statepixel_update::Bool=false) # {{{

    cont, cc_cont = slider_data.slider_container, slider_data.container_context

    reaction = (px, py, layout) -> begin
        resp = nothing
        can_layout = cv_get_can_layout(layout)
        cc_can_layout = cv_get_cc_can_layout(layout)
        lx, ly = cv_global2local(can_layout, cont_l, px, py)
        if 0 ≤ lx ≤ cv_width(cont_l.rectangle)  &&
                0 ≤ ly ≤ cv_height(cont_l.rectangle)
            x, y = cv_pixel2math(cont, cont.can_slider_l, lx, ly)
            resp = set_slider_value_func(x)
            cv_paint(cont.cc_can_slider, painter)
            cont.can_slider_l(cc_cont)
            cont_l(cc_can_layout)
        end
        return resp
    end

    redraw_func = layout -> begin
        cc_can_layout = cv_get_cc_can_layout(layout)
        cv_paint(cont.cc_can_slider, painter)
        cont.can_slider_l(cc_cont)
        cont_l(cc_can_layout)
        return nothing
    end

    draw_once_func = layout -> begin
        if cont.decorator_cb !== nothing
            cont.decorator_cb(cc_cont)
        end
        cv_paint(cont.cc_can_slider, painter)
        cont.can_slider_l(cc_cont)
        cc_can_layout = cv_get_cc_can_layout(layout)
        cont_l(cc_can_layout)
        return nothing
    end

    return cv_combine(setup; draw_once_func, redraw_func,
        actionpixel_update=react_to_actionpixel_update ? reaction : missing,
        statepixel_update=react_to_statepixel_update ? reaction : missing)
end # }}}

# vim:syn=julia:cc=79:fdm=marker:sw=4:ts=4:


# region is an iterable subset of dimensions
# spec. an integer, range, tuple, or array

# inplace complex
function plan_fft!(X::CuArray{T,N}, region) where {T<:cufftComplexes,N}
    K = CUFFT_FORWARD
    inplace = true
    xtype = (T == cufftComplex) ? CUFFT_C2C : CUFFT_Z2Z

    pp = _mkplan(xtype, size(X), region)

    cCuFFTPlan{T,K,inplace,N}(pp, X, size(X), region, xtype)
end

function plan_bfft!(X::CuArray{T,N}, region) where {T<:cufftComplexes,N}
    K = CUFFT_INVERSE
    inplace = true
    xtype =  (T == cufftComplex) ? CUFFT_C2C : CUFFT_Z2Z

    pp = _mkplan(xtype, size(X), region)

    cCuFFTPlan{T,K,inplace,N}(pp, X, size(X), region, xtype)
end

# out-of-place complex
function plan_fft(X::CuArray{T,N}, region) where {T<:cufftComplexes,N}
    K = CUFFT_FORWARD
    xtype =  (T == cufftComplex) ? CUFFT_C2C : CUFFT_Z2Z
    inplace = false

    pp = _mkplan(xtype, size(X), region)

    cCuFFTPlan{T,K,inplace,N}(pp, X, size(X), region, xtype)
end

function plan_bfft(X::CuArray{T,N}, region) where {T<:cufftComplexes,N}
    K = CUFFT_INVERSE
    inplace = false
    xtype =  (T == cufftComplex) ? CUFFT_C2C : CUFFT_Z2Z

    pp = _mkplan(xtype, size(X), region)

    cCuFFTPlan{T,K,inplace,N}(pp, X, size(X), region, xtype)
end

# out-of-place real-to-complex
function plan_rfft(X::CuArray{T,N}, region) where {T<:cufftReals,N}
    K = CUFFT_FORWARD
    inplace = false
    xtype =  (T == cufftReal) ? CUFFT_R2C : CUFFT_D2Z

    pp = _mkplan(xtype, size(X), region)

    ydims = collect(size(X))
    ydims[region[1]] = div(ydims[region[1]],2)+1

    rCuFFTPlan{T,K,inplace,N}(pp, X, (ydims...,), region, xtype)
end

function plan_brfft(X::CuArray{T,N}, d::Integer, region::Any) where {T<:cufftComplexes,N}
    K = CUFFT_INVERSE
    inplace = false
    xtype =  (T == cufftComplex) ? CUFFT_C2R : CUFFT_Z2D
    ydims = collect(size(X))
    ydims[region[1]] = d

    pp = _mkplan(xtype, (ydims...,), region)

    rCuFFTPlan{T,K,inplace,N}(pp, X, (ydims...,), region, xtype)
end

# FIXME: plan_inv methods allocate needlessly (to provide type parameters)
# Perhaps use FakeArray types to avoid this.

function plan_inv(p::cCuFFTPlan{T,CUFFT_FORWARD,inplace,N}) where {T,N,inplace}
    X = CuArray{T}(undef, p.sz)
    pp = _mkplan(p.xtype, p.sz, p.region)
    ScaledPlan(cCuFFTPlan{T,CUFFT_INVERSE,inplace,N}(pp, X, p.sz, p.region,
                                                     p.xtype),
               normalization(X, p.region))
end

function plan_inv(p::cCuFFTPlan{T,CUFFT_INVERSE,inplace,N}) where {T,N,inplace}
    X = CuArray{T}(undef, p.sz)
    pp = _mkplan(p.xtype, p.sz, p.region)
    ScaledPlan(cCuFFTPlan{T,CUFFT_FORWARD,inplace,N}(pp, X, p.sz, p.region,
                                                     p.xtype),
               normalization(X, p.region))
end

function plan_inv(p::rCuFFTPlan{T,CUFFT_INVERSE,inplace,N}
                  ) where {T<:cufftComplexes,N,inplace}
    X = CuArray{real(T)}(undef, p.osz)
    Y = CuArray{T}(undef, p.sz)
    xtype = p.xtype == CUFFT_C2R ? CUFFT_R2C : CUFFT_D2Z
    pp = _mkplan(xtype, p.osz, p.region)
    ScaledPlan(rCuFFTPlan{real(T),CUFFT_FORWARD,inplace,N}(pp, X, p.sz, p.region,
                                                     xtype),
               normalization(X, p.region))
end

function plan_inv(p::rCuFFTPlan{T,CUFFT_FORWARD,inplace,N}
                  ) where {T<:cufftReals,N,inplace}
    X = CuArray{complex(T)}(undef, p.osz)
    Y = CuArray{T}(undef, p.sz)
    xtype = p.xtype == CUFFT_R2C ? CUFFT_C2R : CUFFT_Z2D
    pp = _mkplan(xtype, p.sz, p.region)
    ScaledPlan(rCuFFTPlan{complex(T),CUFFT_INVERSE,inplace,N}(pp, X, p.sz,
                                                              p.region, xtype),
               normalization(Y, p.region))
end

# The rest of the standard API

size(p::CuFFTPlan) = p.sz

function mul!(y::CuArray{Ty}, p::CuFFTPlan{T,K,false}, x::CuArray{T}
                  ) where {Ty,T,K}
    assert_applicable(p,x,y)
    unsafe_execute!(p,x,y)
    return y
end

function *(p::cCuFFTPlan{T,K,true,N}, x::CuArray{T,N}) where {T,K,N}
    assert_applicable(p,x)
    unsafe_execute!(p,x)
    x
end

function *(p::rCuFFTPlan{T,CUFFT_FORWARD,false,N}, x::CuArray{T,N}
           ) where {T<:cufftReals,N}
    @assert p.xtype ∈ [CUFFT_R2C,CUFFT_D2Z]
    y = CuArray{complex(T),N}(undef, p.osz)
    mul!(y,p,x)
    y
end

function *(p::rCuFFTPlan{T,CUFFT_INVERSE,false,N}, x::CuArray{T,N}
           ) where {T<:cufftComplexes,N}
    @assert p.xtype ∈ [CUFFT_C2R,CUFFT_Z2D]
    y = CuArray{real(T),N}(undef, p.osz)
    mul!(y,p,x)
    y
end

function *(p::cCuFFTPlan{T,K,false,N}, x::CuArray{T,N}) where {T,K,N}
    y = CuArray{T,N}(undef, p.osz)
    mul!(y,p,x)
    y
end


"""
genetrap.jl

Fit GRSM models to live cell and smFISH data
"""

"""
Gene information
"""
const genes = ["CANX";"DNAJC5";"ERRFI1";"KPNB1";"MYH9";"Rab7a";"RHOA";"RPAP3";"Sec16A";"SLC2A1"]
const genelength = Dict([("Sec16A", 42960);("SLC2A1", 33802);("ERRFI1", 14615);("RHOA", 52948);("KPNB1", 33730);("MYH9", 106741);("DNAJC5", 40930);("CANX", 32710);("Rab7a", 88663);("RPAP3", 44130)])
const MS2end = Dict([("Sec16A", 5220);("SLC2A1", 26001);("ERRFI1", 5324);("RHOA", 51109);("KPNB1", 24000);("MYH9", 71998);("DNAJC5", 14857);("CANX", 4861);("Rab7a", 83257);("RPAP3", 38610)])
const halflife = Dict([("CANX", 50.),("DNAJC5", 5.),("ERRFI1", 1.35),("KPNB1", 9.),("MYH9", 10.),("Rab7a", 50.),("RHOA", 50.),("RPAP3", 7.5),("Sec16A", 8.),("SLC2A1", 5.)])

"""
    fit_genetrap()

"""

function makeswarm_genetrap(sfile,juliafile,nchains,genes::Vector,G::Int,R::Int,nalleles::Int,root::String,folder::String,maxtime::Float64,samplesteps::Int)
write_swarmfile(sfile,nchains,juliafile,genes)
write_fitfile(juliafile,nchains,G,R,nalleles,root,folder,maxtime,samplesteps)
end

function write_fitfile(fitfile,nchains,G::Int,R::Int,nalleles::Int,root::String,folder::String,maxtime::Float64,samplesteps::Int)
        f = open(fitfile,"w")
        s =   '"'
        write(f,"@everywhere using StochasticGene\n")
        write(f,"@time fit_genetrap($nchains,ARGS[1],$G,$R,$nalleles,$s$root$s,$s$folder$s,$maxtime,$samplesteps)\n")
        close(f)
end

function fit_genetrap(nchains,gene::String,G::Int,R::Int,nalleles::Int,root::String,folder::String,maxtime::Float64,samplesteps::Int)
    println(now())
    data,model,options = genetrap(root,folder,"mean","gt",gene,G,R,nalleles,"",maxtime,samplesteps)
    print_ll(data,model)
    fit,stats,waic = run_mh(data,model,options,nchains)
    finalize(data,model,fit,stats,waic,1.,folder,root)
    println(now())
end

"""
genetrap()

Load data, model and option structures for metropolis-hastings fit
root is the folder containing data and results

FISH counts is divided by tempfish to adjust relative weights of data
set tempfish = 0 to equalize FISH and live cell counts
"""
function genetrap(root::String,infolder::String,rtype::String,label::String,gene::String,G::Int,R::Int,nalleles::Int,type::String,maxtime::Float64,samplesteps::Int,cyclesteps=0,annealsteps=0,warmupsteps=0,method=1,temp=1.,tempanneal=1.,tempfish=1.)
    # r = readrates_genetrap(root,infolder,rtype,gene,"$G$R",type)
    r = readrates_genetrap(joinpath(root,infolder),rtype,gene,label,G,R,nalleles,type)
    println(r)
    genetrap(root,r,label,gene,G,R,nalleles,type,maxtime,samplesteps,cyclesteps,annealsteps,warmupsteps,method,temp,tempanneal,tempfish)
end

function genetrap(root,r,label::String,gene::String,G::Int,R::Int,nalleles::Int,type::String,maxtime::Float64,samplesteps::Int,cyclesteps=0,annealsteps=0,warmupsteps=0,method=1,temp=1.,tempanneal=1.,tempfish=1.)
    data = iszero(R) || iszero(tempfish) ? data_genetrap_FISH(root,label,gene) : data_genetrap(root,label,gene,tempfish)
    model = model_genetrap(r,gene,G,R,nalleles,type,method)
    options = MHOptions(samplesteps,cyclesteps,warmupsteps,annealsteps,maxtime,temp,tempanneal)
    return data,model,options
end

function data_genetrap(root,label,gene,tempfish=1.)
    LC = readLCPDF_genetrap(root,gene)
    if tempfish == 0
        counts = Int(div(sum(LC[:,2]+LC[:,3]),2))
        println(counts)
        # set FISH counts to mean of live cell counts
        histFISH = readFISH_genetrap(root,gene,counts)
    else
        histFISH = readFISH_genetrap(root,gene,tempfish)
    end
    RNALiveCellData(label,gene,length(histFISH),histFISH,LC[:,1],LC[:,3],LC[:,2])
end

function data_genetrap_FISH(root,label,gene)
    histFISH = readFISH_genetrap(root,gene,1.)
    RNAData(label,gene,length(histFISH),histFISH)
end

"""
model_genetrap

load model structure
"""
function model_genetrap(r,gene::String,G::Int,R::Int,nalleles::Int,type::String,method,)
    if R == 0
        fittedparam = collect(1:2*G-1)
        decayrate = get_decay(halflife[gene])
        if r == 0
            r = setrate(G,1,decayrate,.1)[1]
        end
        d = prior_rna(r,G,1,fittedparam,decayrate,1.)
        return  GMmodel{typeof(r),typeof(d),Float64,typeof(fittedparam),Int}(G,nalleles,r,d,0.02,fittedparam,1)
    else
        propcv=[.02*ones(2*(G-1));1e-4;1e-4*ones(R);.02*ones(R)]
        fittedparam=collect(1:num_rates(G,R)-1)
        Gprior=(.01,.5)
        Sprior=(.1,.5)
        Rcv=1e-2
        rm,rcv = prior_rates_genetrap(G,R,gene,Gprior,Sprior,Rcv)
        if r == 0
            r = rm
        end
        # d = priordistributionLogNormal_genetrap(rm,rcv,G,R)
        d = priordistributionGamma_genetrap(rm,rcv,G,R)
        return GRSMmodel{typeof(r),typeof(d),typeof(propcv),typeof(fittedparam),typeof(method)}(G,R,nalleles,type,r,d,propcv,fittedparam,method)
    end
end

"""
setpriorrate(G,R,gene,Gprior::Tuple,Sprior::Tuple,Rcv::Float64)
Set prior distribution for mean and cv of rates
"""
function prior_rates_genetrap(G,R,gene,Gprior::Tuple,Sprior::Tuple,Rcv::Float64)
    n = G-1
    rm = [fill(Gprior[1],2*n);2000/(genelength[gene]-MS2end[gene]);fill(2000/MS2end[gene]*R,R);fill(Sprior[1], R);log(2.)/(60 .* halflife[gene])]
    rcv = [fill(Gprior[2],2*n);Rcv;Rcv;fill(Sprior[2], R);Rcv]
    return rm,rcv
end

"""
priordistributionLogNormal_genetrap(r,cv,G,R)

LogNormal Prior distribution
"""
function priordistributionLogNormal_genetrap(r,cv,G,R)
    sigma = sigmalognormal(cv)
    d = []
    j = 1
    #  G rates
    for i in Grange(G)
        push!(d,Distributions.LogNormal(log(r[i]),sigma[j]))
        j += 1
    end
    # initiation rate
    i = initiation(G)
    push!(d,Distributions.LogNormal(log(r[i]),sigma[j]))
    j += 1
    # Step rates are bounded by length of insertion site to end of gene, i.e sum of reciprocal rates is bounded
    t = sum(1 ./ r[Rrange(G,R)])
    push!(d,Distributions.LogNormal(-log(t),sigma[j]))
    j += 1
    # priors for splice rates
    rs = 0.
    for i in Srange(G,R)
        rs += r[i]
        push!(d,Distributions.LogNormal(log(rs),sigma[j]))
        j += 1
    end
    return d
end

"""
priordistributionGamma_genetrap(r,cv,G,R)

Gamma Prior distribution
"""
function priordistributionGamma_genetrap(rm,cv,G,R)
    n = G-1
    zet = R
    d = []
    rsig = cv .^ 2
    j = 1
    # priors for G rates
    for i in Grange(G)
        theta = rsig[j]*rm[i]
        alpha = 1/rsig[j]
        push!(d,Distributions.Gamma(alpha,theta))
        j += 1
    end
    # prior for nu1 is upper bounded by rm, i.e. distance is bounded by distance to insertion site
    theta = rsig[j]*rm[initiation(G)]
    alpha = 1/rsig[j]
    push!(d,Distributions.Gamma(alpha,theta))
    j += 1
    # priors for nu rates are bounded by length of insertion site to end of gene, i.e sum of reciprocal rates is bounded
    tm = sum(1 ./ rm[Rrange(G,R)])
    # for i in 2*n + 2 : 2*n + zet + 1
    #     tm += 1/rm[i]
    # end
    theta = rsig[j]/tm
    alpha = 1/rsig[j]
    push!(d,Distributions.Gamma(alpha,theta))
    j += 1
    # priors for ejection rates
    rs = 0
    for i in Srange(G,R)
        rs += rm[i]
        theta = rsig[j]*rs
        alpha = 1/rsig[j]
        push!(d,Distributions.Gamma(alpha,theta))
        j += 1
    end
    return d
end

"""
readLCPDF_genetrap(root,gene)

Read in dwell time PDF
"""
function readLCPDF_genetrap(root,gene)
    infile = joinpath(root,"DwellTimePDF/$(gene)_PDF.csv")
    if isfile(infile)
        LC = readdlm(infile,',')
        x = truncate_histogram(LC[:,2],.999,1000)
        LC[1:length(x),:]
    else
        println("No data for gene: ", gene)
    end
end

"""
readFISH_genetrap(root,gene,temp=1.,clone=true)

Read and assemble smFISH data
"""
function readFISH_genetrap(root::String,gene::String,temp::Float64=1.,clone=true)
    counts = countsFISH_genetrap(root,gene,clone)
    readFISH_genetrap(root,gene,Int(div(counts,temp)),clone)
end

function readFISH_genetrap(root::String,gene::String,counts::Int,clone=true)
    fishfile=joinpath(root,"Fish_4_Genes/$(gene)_steady_state_mRNA.csv")
    col = clone ? 3 : 2
    # Get smFISH RNA histograms
    if isfile(fishfile)
        x = readdlm(fishfile,',')[3:end,col]
        x = x[x .!= ""]
        x = truncate_histogram(x,.99,1000)
        x /= sum(x)
        x *= counts
        return x
    else
        println("No smFISH data for gene: ", gene)
    end
    nothing
end

"""
countsFISH_genetrap(root,gene,x,counts,clone=true)

read in smFISH counts
"""
function countsFISH_genetrap(root,gene,clone=true)
    countfile = joinpath(root,"counts/total_counts.csv")
    wttext = "WT_"
    clonetext = "_clone"
    if isfile(countfile)
        countsdata = readdlm(countfile,',')[:,:]
        if clone
            counts=(countsdata[findall(uppercase("$(gene)$clonetext") .== uppercase.(countsdata[:,1])),2])[1]
        else
            counts=(countsdata[findall("$wttext$(gene)" .== countsdata[:,1]),2])[1]
        end
    else
        counts = 1000
    end
    return counts
end

"""
readrates_genetrap(infolder::String,rtype::String,gene::String,model::String,type::String)

Read in initial rates from previous runs
"""
# function readrates_genetrap(infolder::String,rtype::String,gene::String,label,G,R,nalleles,type::String)
#     if rtype == "rml" || rtype == "rlast"
#         rskip = 1
#         rstart = 1
#         row = 1
#     elseif rtype == "rmean"
#         rskip = 2
#         rstart = 1
#     elseif rtype == "rquant"
#         rskip = 3
#         rstart = 2
#     end
#     if type == "offeject" || type == "on"
#         type = ""
#     end
#     infile = getratefile_genetrap(infolder,rtype,gene,label,G,R,nalleles,type)
#     print(gene," ","$G$R"," ",type)
#     readrates_genetrap(infile,rstart,rskip)
# end

function readrates_genetrap(infolder::String,rtype::String,gene::String,label,G,R,nalleles,type::String)
    if rtype == "ml"
        row = 1
    elseif rtype == "mean"
        row = 2
    elseif rtype == "median"
        row = 3
    elseif rtype == "last"
        row = 4
    else
        row = 2
    end
    if type == "offeject" || type == "on"
        type = ""
    end
    infile = getratefile_genetrap(infolder,rtype,gene,label,G,R,nalleles,type)
    println(gene," ","$G$R"," ",type)
    readrates_genetrap(infile,row)
end

function readrates_genetrap(infile::String,row::Int)
    if isfile(infile) && ~isempty(read(infile))
        return readrates(infile,row)
    else
        println(" no prior")
        return 0
    end
end

# function readrates_genetrap(infile::String,rstart::Int,rskip::Int)
#     if isfile(infile) && ~isempty(read(infile))
#         r = readdlm(infile)
#         r = r[:,rstart:rskip:end]
#         println(r)
#         return r
#     else
#         println(" no prior")
#         return 0
#     end
#     nothing
# end

function getratefile_genetrap(infolder::String,rtype::String,gene::String,label,G,R,nalleles,type::String)
    model = R == 0 ? "$G" : "$G$R"
    file = "rates" * "_" * label * "_" * gene * "_" * model * "_"  * "$(nalleles)" * ".txt"
    joinpath(infolder,file)
end

# function getratefolder_genetrap(root::String,infolder::String,rtype::String,gene::String,model::String,type::String)
#     results = joinpath(root,"results")
#     infolder = joinpath(infolder,gene)
#     infolder = joinpath(infolder,rtype * model * type * txtstr)
#     joinpath(results,infolder)
# end


__precompile__()

module QMTK
using Compat
using Compat.SparseArrays
using Compat.Random

"""
    __version__

QMTK version number
"""
const __version__ = v"0.1.0"
# NOTE: remember to replace `include` when issue #4600
# is solved

# orthogonal part
include("Consts/Consts.jl")
# include("Utils.jl")
include("Base/Base.jl")

# Depends on Base.jl
include("Lattice/Lattice.jl")
include("Sample/Sample.jl")

# Hamiltonian (depends on Lattice.jl, Basis.jl)
include("Hamiltonian/Hamiltonian.jl")

# Model
include("Model/Model.jl")

# Kronnecker Sugar
include("Kron.jl")

end # module


@inline function tostr(val::Transition)
    hval = Cell(val >> BSHIFT_CELL)
    lval = Cell(val & BMASK_C)
    string(tostr(hval),tostr(lval))
end

@inline function tostr(val::Cell)
    string((Char((val >> BSHIFT_NORTH) & BMASK_P)),
           (Char((val >> BSHIFT_EAST) & BMASK_P)),
           (Char((val >> BSHIFT_SOUTH) & BMASK_P)),
           (Char((val >> BSHIFT_WEST) & BMASK_P)))
end

@inline function toval64(str::AbstractString)
    @assert(length(str)==sizeof(UInt64), length(str))
    hval = toval32(str[1:4])
    lval = toval32(str[5:8])
    Transition(hval) << BSHIFT_CELL|Transition(lval)
end

@inline function toval32(str::AbstractString)
    @assert(length(str)==sizeof(UInt32))
    Cell(str[1]) << BSHIFT_NORTH|
    Cell(str[2]) << BSHIFT_EAST|
    Cell(str[3]) << BSHIFT_SOUTH|
    Cell(str[4]) << BSHIFT_WEST
end

const loadfuncs = Dict(:CellSpace=>load_cell, :Rule=>load_rule)
function load(fname::String, typ::Symbol)
    if haskey(loadfuncs, typ)
        loadfuncs[typ](fname)
    else
        throw("no function for $typ")
    end
end

const updatefuncs = Dict(:Checkerboard=>update_checkerboard!, :Random=>update_random!)
function update!(cs::CellSpace, rule::Rule, typ::Symbol)
    if haskey(updatefuncs, typ)
        updatefuncs[typ](cs, rule)
    else
        throw("no function for $typ")
    end
end


module TestExpressionsTupleForm

import Test: @testset, @test
import Volcanito: tuple_form, ColumnName
import MacroTools: postwalk, rmlines

@testset "tuple_form" begin
    input = tuple_form(
        :(x + y),
        (ColumnName(:x, false), ColumnName(:y, false)),
    )

    output = :(t -> $(Expr(:escape, :+))(t[1], t[2]))

    @test postwalk(rmlines, input) == postwalk(rmlines, output)

    input = tuple_form(
        :(y + x + $(Expr(:$, :z))),
        (ColumnName(:x, false), ColumnName(:y, false)),
    )

    output = :(t -> $(Expr(:escape, :+))(t[2], t[1], $(Expr(:escape, :z))))

    @test postwalk(rmlines, input) == postwalk(rmlines, output)
end

end


function solve()
    n = parse(Int, readline())
    cs = readline()

    numr = count("R", cs)
    count("W", cs[1:numr])
end

println(solve())


@benchset "kernel" begin

    @benchset "ZeroKernel{Float64}()" begin
        create_benchmarks(ZeroKernel{Float64}(); grads=false)
    end
    # @benchset "OneKernel()" begin
    #     create_benchmarks(OneKernel(); grads=false)
    # end
    # @benchset "EQ" begin
    #     @benchset "Real CPU" create_benchmarks(EQ())
        # @benchset "Real GPU" create_benchmarks(
        #     EQ();
        #     x=randn(Float32), x′=randn(Float32),
        #     x̄s=[CuArray{Float32}(randn(N)) for N in Ns()],
        #     x̄′s=[CuArray{Float32}(randn(N)) for N in Ns()],
        # )

    #     for D in Ds()
    #         @benchset "ColVecs (D=$D) CPU" create_benchmarks(
    #             EQ();
    #             x=randn(D), x′=randn(D),
    #             x̄s=[ColVecs(randn(D, N)) for N in Ns()],
    #             x̄′s=[ColVecs(randn(D, N)) for N in Ns()],
    #         )
    #     end

    #     # See https://github.com/FluxML/Zygote.jl/issues/44
    #     @benchset "EQ Almost-Toeplitz" create_benchmarks(
    #         EQ();
    #         x=randn(), x′=randn(),
    #         x̄s=[range(-randn(), step=randn(), length=N) for N in Ns()],
    #         x̄′s=[range(-randn(), step=randn(), length=N) for N in Ns()],
    #     )

    #     δ = randn()
    #     @benchset "EQ Toeplitz" create_benchmarks(
    #         EQ();
    #         x=randn(), x′=randn(),
    #         x̄s=[range(-randn(), step=δ, length=N) for N in Ns()],
    #         x̄′s=[range(-randn(), step=δ, length=N) for N in Ns()],
    #     )
    # end

    # @benchset "PerEQ" begin
    #     @benchset "Real CPU" create_benchmarks(PerEQ())
    #     # @benchset "Real GPU" create_benchmarks(PerEQ();
    #     #     x = randn(Float32), x′=randn(Float32),
    #     #     x̄s=[CuArray{Float32}(randn(N)) for N in Ns()],
    #     #     x̄′s=[CuArray{Float32}(randn(N)) for N in Ns()],
    #     # )
    # end
end


#=
Copyright (c) 2018-2022 Chris Coey, Lea Kapelevich, and contributors

This Julia package Hypatia.jl is released under the MIT license; see LICENSE
file in the root directory or at https://github.com/chriscoey/Hypatia.jl
=#

relaxed_tols = (default_tol_relax = 100,)
insts = OrderedDict()
insts["minimal"] = [
    ((2, true),),
    ((2, false),),
    ]
insts["fast"] = [
    ((20, true),),
    ((20, false),),
    ((30, true),),
    ((30, false),),
    ]
insts["various"] = vcat(insts["fast"], [
    ((40, true), nothing, relaxed_tols),
    ((40, false), nothing, relaxed_tols),
    ])
return (StabilityNumber, insts)


mutable struct Tape
  tape::Array{Int, 1}
  pos::Int

  Tape() = new(zeros(Int, 1), 1)
end

inc(this::Tape) = (this.tape[this.pos] += 1)
dec(this::Tape) = (this.tape[this.pos] -= 1)
get(this::Tape) = this.tape[this.pos]

function advance(this::Tape)
  this.pos += 1
  if this.pos > length(this.tape)
    push!(this.tape, 0)
  end
end

function devance(this::Tape)
  if this.pos > 1
    this.pos -= 1
  end
end

validbfsymbol(x) = in(x, ['>', '<', '+', '-', '.', ',', '[', ']'])

struct Program
  code::String
  bracket_map::Dict{Int, Int}

  function Program(text)
    code = filter(validbfsymbol, text)
    bracket_map = Dict{Int, Int}()
    stack = Int[]
    pc = 1
    for ch in code
      if ch == '['
        push!(stack, pc)
      elseif ch == ']' && length(stack) > 0
        right = pop!(stack)
        bracket_map[pc] = right
        bracket_map[right] = pc
      end
      pc += 1
    end
    return new(code, bracket_map)
  end
end

function run(this::Program)
  code = this.code
  bracket_map = this.bracket_map
  tape = Tape()
  pc = 1
  while pc <= length(code)
    ch = code[pc]
    if ch == '+'
      inc(tape)
    elseif ch == '-'
      dec(tape)
    elseif ch == '>'
      advance(tape)
    elseif ch == '<'
      devance(tape)
    elseif ch == '['
      if get(tape) == 0
        pc = bracket_map[pc]
      end
    elseif ch == ']'
      if get(tape) != 0
        pc = bracket_map[pc]
      end
    elseif ch == '.'
      print(Char(get(tape)))
    end
    pc += 1
  end
end

function main(text)
  run(Program(text))
end

println("JIT warming up")
main(">++[<+++++++++++++>-]<[[>+>+<<-]>[<+>-]++++++++[>++++++++<-]>[-]<<>++++++++++[>++++++++++[>++++++++++[>++++++++++[>++++++++++[>++++++++++[>++++++++++[-]<-]<-]<-]<-]<-]<-]<-]++++++++++")

println("bench")
text = open(ARGS[1]) do file
  read(file, String)
end
x = @timed main(text)
println("Elapsed: $(x[2]), Allocated: $(x[3]), GC Time: $(x[4])")