text:
"""
```
forecast(m, system, z0; cond_type = :none,
    enforce_zlb = false, shocks = Matrix{S}(undef, 0,0))

forecast(m, altpolicy, z0, states, obs, pseudo, shocks)

forecast(system, z0, shocks; enforce_zlb = false)

forecast(m, system, z0, shocks; cond_type = :none, enforce_zlb = false)
```

The first method produces a forecast, given a state space system,
initial state, and shocks, using information about the desired forecast
contained in `m`. It enforces the ZLB by using monetary policy shocks.

The second method is similar but differs in two ways. First,
it produces forecasts specifically when an alternative policy
is used. Second, it enforces the ZLB by treating it as a temporary
alternative policy.

The third and fourth methods are internal functions used by the first two methods.

### Inputs

- `system::System{S}`: state-space system matrices
- `z0::Vector{S}`: state vector in the final historical period
- `shocks::Matrix{S}`: `nshocks` x `nperiods` matrix of shocks to use when
  forecasting. Note that in the first method, `nperiods` doesn't necessarily
  have to equal `forecast_horizons(m)`; it will be truncated or padded with
  zeros appropriately

**Method 1 and 2 only:**

- `m::AbstractDSGEModel`

**Method 2 only:**

- `altpolicy::Symbol`: Which alternative policy is being used
- `obs::Matrix{S <: Real}`: matrix of forecasted observables

where `S<:AbstractFloat`.

### Keyword Arguments

- `cond_type::Symbol`: one of `:none`, `:semi`, or `:full`, used to determine
  how many periods to forecast ahead. If `cond_type in [:semi, :full]`, the
  forecast horizon is reduced by the number of periods of conditional
  data. Defaults to `:none`.
- `enforce_zlb::Bool`: whether to enforce the zero lower bound. Defaults to
  `false`.
- `shocks::Matrix{S}`: matrix of size `nshocks` x `shock_horizon` of shock
  innovations under which to forecast. If `shock_horizon > horizon`, the extra
  periods of shocks will be ignored; if `shock_horizon < horizon`, zeros will be
  filled in for the shocks hitting the remaining forecasted periods.
- `draw_shocks::Bool`: if `isempty(shocks)`, indicates whether to draw shocks
  according to:

  1. If `forecast_tdist_shocks(m)`, draw `horizons` many shocks from a
     `Distributions.TDist(forecast_tdist_df_val(m))`
  2. Otherwise, draw `horizons` many shocks from a
     `DegenerateMvNormal(zeros(nshocks), sqrt(system[:QQ]))`

  or to set `shocks` to a `nshocks` x `horizon` matrix of zeros. Defaults to
  `false`. If `shocks` is provided as a keyword argument, this flag has no
  effect.

**Method 2 only:**

- `set_zlb_regime_vals::Function`: user-provided function that adds additional regimes to
    regime-switching parameters if not enough regimes exist to impose the ZLB
    as a temporary alternative policy. Defaults to `identity`, and nothing will happen
    if this is the case.

- `tol::{<: Real}`: Tolerance for the smallest permissible value for the nominal interest rate.
    Defaults to -1e-14.

### Outputs

- `states::Matrix{S}`: matrix of size `nstates` x `horizon` of forecasted states
- `obs::Matrix{S}`: matrix of size `nobs` x `horizon` of forecasted observables
- `pseudo::Matrix{S}`: matrix of size `npseudo` x `horizon` of forecasted
  pseudo-observables
- `shocks::Matrix{S}`: matrix of size `nshocks` x `horizon` of shock innovations
"""
function forecast(m::AbstractDSGEModel, system::Union{RegimeSwitchingSystem{S}, System{S}},
                  z0::Vector{S}; cond_type::Symbol = :none, enforce_zlb::Bool = false,
                  shocks::AbstractMatrix{S} = Matrix{S}(undef, 0, 0), draw_shocks::Bool = false) where {S<:AbstractFloat}

    # Numbers of things
    nshocks = n_shocks_exogenous(m)
    horizon = forecast_horizons(m; cond_type = cond_type)

    if isempty(shocks)
        # Populate shocks matrix
        if draw_shocks
            # If Regime Switching System, need to get proper QQ in each forecast period.
            if isa(system, RegimeSwitchingSystem)
                shocks = zeros(nshocks, horizon)
                μ = zeros(nshocks)
                # Forecast regime indices (starting with 1 in 1st fcast period, going up to horizon)
                regime_inds = get_fcast_regime_inds(m, horizon, cond_type)
                # The regime in which forecast starts (indexing from presample_start as reg=1)
                sys_ind = cond_type == :none ? get_setting(m, :reg_forecast_start) :
                max(get_setting(m, :reg_forecast_start), get_setting(m, :reg_post_conditional_end))
                for ts in regime_inds
                    σ = sqrt.(system[sys_ind, :QQ])
                    dist = if forecast_tdist_shocks(m)
                        # Use t-distributed shocks
                        ν = forecast_tdist_df_val(m)
                        DegenerateDiagMvTDist(μ, σ, ν)
                    else
                        # Use normally distributed shocks
                        DegenerateMvNormal(μ, σ)
                    end
                    shocks[:, ts] = rand(dist, length(ts))
                    sys_ind += 1
                end
            else
                μ = zeros(S, nshocks)
                σ = sqrt.(system[:QQ])
                dist = if forecast_tdist_shocks(m)
                    # Use t-distributed shocks
                    ν = forecast_tdist_df_val(m)
                    DegenerateDiagMvTDist(μ, σ, ν)
                else
                    # Use normally distributed shocks
                    DegenerateMvNormal(μ, σ)
                end
                shocks = rand(dist, horizon)
            end

            # Forecast without anticipated shocks
            if n_mon_anticipated_shocks(m) > 0
                ind_ant1 = m.exogenous_shocks[Symbol(get_setting(m, :monetary_policy_shock), :l1)]
                ind_antn = m.exogenous_shocks[Symbol(get_setting(m, :monetary_policy_shock), "l$(n_mon_anticipated_shocks(m))")]
                ant_shock_inds = ind_ant1:ind_antn
                shocks[ant_shock_inds, :] .= 0
            end
        else
            shocks = zeros(S, nshocks, horizon)
        end
    else
        # Adjust size of shocks matrix, padding with zeros or cutting off
        # periods of shocks if necessary
        shock_horizon = size(shocks, 2)
        if shock_horizon <= horizon
            shocks0 = zeros(nshocks, horizon - shock_horizon)
            shocks = hcat(shocks, shocks0)
        else
            shocks = shocks[:, 1:horizon]
        end
    end

    if haskey(get_settings(m), :n_periods_no_shocks)
        if get_setting(m, :n_periods_no_shocks) > 0
            shocks[:, 1:get_setting(m, :n_periods_no_shocks)] = zeros(size(shocks, 1), get_setting(m, :n_periods_no_shocks))
        end
    end

    # Populate shocks matrix under alternative policy, if
    # user has specified a function to do so
    #=    alt_policy = alternative_policy(m)

    # TODO: check that these handling of the altpolicy cases work properly
    if (alt_policy.solve != identity &&
    alt_policy.forecast_init != identity && (!haskey(m.settings, :initialize_pgap_ygap) || get_setting(m, :initialize_pgap_ygap)))
    shocks, z0 = alt_policy.forecast_init(m, shocks, z0, cond_type = cond_type)
    end=#

    #=
    # This code block has been moved to forecast_one_draw in drivers.jl
    # b/c we want to actually alter the smoothed states so that pgap or ygap
    # starts from the user-specified gap value and that this start is actually recorded.
    # TODO: check that these handling of the altpolicy cases work properly
    # Added separately to cover case where you're not using altpolicy but are using gensys2
    if haskey(m.settings, :pgap_type) && haskey(get_settings(m), :pgap_value)
    if get_setting(m, :pgap_type) == :ngdp
    _, z0 = ngdp_forecast_init(m, shocks, z0, cond_type = cond_type)
    end
    end

    if haskey(m.settings, :ygap_type) && haskey(get_settings(m), :ygap_value)
    if get_setting(m, :ygap_type) == :smooth_ait_gdp
    _, z0 = smooth_ait_gdp_forecast_init(m, shocks, z0, cond_type = cond_type)
    end
    end
    =#

    # Get variables necessary to enforce the zero lower bound in the forecast
    ind_r = m.observables[get_setting(m, :nominal_rate_observable)]
    ind_r_sh = m.exogenous_shocks[get_setting(m, :monetary_policy_shock)]
    zlb_value = forecast_zlb_value(m)

    if isa(system, RegimeSwitchingSystem)
        forecast(m, system, z0, shocks; cond_type = cond_type, enforce_zlb = enforce_zlb,
                 ind_r = ind_r, ind_r_sh = ind_r_sh, zlb_value = zlb_value)
    else
        forecast(system, z0, shocks; enforce_zlb = enforce_zlb,
                 ind_r = ind_r, ind_r_sh = ind_r_sh, zlb_value = zlb_value)
    end
end

function forecast(system::System{S}, z0::Vector{S},
                  shocks::Matrix{S}; enforce_zlb::Bool = false, ind_r::Int = -1,
                  ind_r_sh::Int = -1, zlb_value::S = 0.1/4) where {S<:AbstractFloat}

    # Unpack system
    T, R, C = system[:TTT], system[:RRR], system[:CCC]
    Q, Z, D = system[:QQ], system[:ZZ], system[:DD]
    Z_pseudo, D_pseudo = system[:ZZ_pseudo], system[:DD_pseudo]

    # Setup
    nshocks = size(R, 2)
    nstates = size(T, 2)
    nobs    = size(Z, 1)
    npseudo = size(Z_pseudo, 1)
    horizon = size(shocks, 2)

    # Define our iteration function
    function iterate(z_t1, ϵ_t)
        z_t = C + T*z_t1 + R*ϵ_t

        # Change monetary policy shock to account for 0.13 interest rate bound
        if enforce_zlb
            interest_rate_forecast = getindex(D + Z*z_t, ind_r)
            if interest_rate_forecast < zlb_value
                # Solve for interest rate shock causing interest rate forecast to be exactly ZLB
                ϵ_t[ind_r_sh] = 0.
                z_t = C + T*z_t1 + R*ϵ_t
                ϵ_t[ind_r_sh] = getindex((zlb_value - D[ind_r] - Z[ind_r, :]'*z_t) / (Z[ind_r, :]' * R[:, ind_r_sh]), 1)

                # Forecast again with new shocks
                z_t = C + T*z_t1 + R*ϵ_t

                # Confirm procedure worked
                interest_rate_forecast = getindex(D + Z*z_t, ind_r)
                @assert interest_rate_forecast >= zlb_value - 0.01 "interest_rate_forecast = $interest_rate_forecast must be >= zlb_value - 0.01 = $(zlb_value - 0.01)"
            end
        end
        return z_t, ϵ_t
    end

    # Iterate state space forward
    states = zeros(S, nstates, horizon)
    states[:, 1], shocks[:, 1] = iterate(z0, shocks[:, 1])
    for t in 2:horizon
        states[:, t], shocks[:, t] = iterate(states[:, t-1], shocks[:, t])
    end

    # Apply measurement and pseudo-measurement equations
    obs    = D .+ Z*states
    pseudo = D_pseudo .+ Z_pseudo * states

    # Return forecasts
    return states, obs, pseudo, shocks
end

function forecast(m::AbstractDSGEModel, system::RegimeSwitchingSystem{S}, z0::Vector{S},
                  shocks::Matrix{S}; cond_type::Symbol = :none, enforce_zlb::Bool = false, ind_r::Int = -1,
                  ind_r_sh::Int = -1, zlb_value::S = 0.1/4) where {S<:AbstractFloat}

    # Determine how many regimes occur in the forecast, depending
    # on whether we need to subtract conditional forecast regimes or not
    n_fcast_reg = get_setting(m, :n_fcast_regimes)
    if cond_type != :none
        n_fcast_reg -= get_setting(m, :reg_post_conditional_end) - # Remove number of regimes switched since starting
        get_setting(m, :reg_forecast_start)                    # the conditional forecast
        if n_fcast_reg == 0 # Then no new regimes after conditional forecast ends
            n_fcast_reg = 1 # So we use the last conditional forecast regime
        end
    end

    Ts = Vector{Matrix{Float64}}(undef, n_fcast_reg)
    Rs = Vector{Matrix{Float64}}(undef, n_fcast_reg)
    Cs = Vector{Vector{Float64}}(undef, n_fcast_reg)
    Qs = Vector{Matrix{Float64}}(undef, n_fcast_reg)
    Zs = Vector{Matrix{Float64}}(undef, n_fcast_reg)
    Ds = Vector{Vector{Float64}}(undef, n_fcast_reg)
    Z_pseudos = Vector{Matrix{Float64}}(undef, n_fcast_reg)
    D_pseudos = Vector{Vector{Float64}}(undef, n_fcast_reg)

    # Determine in which regime the forecast starts, after accounting for
    # conditional forecast regimes, if applicable
    reg_fcast_cond_start = (cond_type == :none) ? get_setting(m, :reg_forecast_start) :
    max(get_setting(m, :reg_forecast_start), get_setting(m, :reg_post_conditional_end))

    # n_regimes is incorrectly being computed, as is n_fcast_reg
    # Unpack system
    for (ss_ind, sys_ind) in enumerate(reg_fcast_cond_start:get_setting(m, :n_regimes))
        Ts[ss_ind], Rs[ss_ind], Cs[ss_ind] = system[sys_ind, :TTT], system[sys_ind, :RRR], system[sys_ind, :CCC]
        Qs[ss_ind], Zs[ss_ind], Ds[ss_ind] = system[sys_ind, :QQ], system[sys_ind, :ZZ], system[sys_ind, :DD]
        Z_pseudos[ss_ind], D_pseudos[ss_ind] = system[sys_ind, :ZZ_pseudo], system[sys_ind, :DD_pseudo]
    end

    # Setup
    nshocks = size(Rs[1], 2)
    nstates = size(Ts[1], 2)
    nobs    = size(Zs[1], 1)
    npseudo = size(Z_pseudos[1], 1)
    horizon = size(shocks, 2)

    # Define our iteration function
    check_has_unant_mp_sh = (haskey(get_settings(m), :gensys2) ? get_setting(m, :gensys2) : false) &&
    (!isempty(intersect([:zlb_rule, :zero_rate], [x.alternative_policy.key for x in values(get_setting(m, :regime_eqcond_info))])))
    function iterate(z_t1, ϵ_t, T, R, C, Q, Z, D)
        z_t = C + T*z_t1 + R*ϵ_t

        # Change monetary policy shock to account for 0.13 interest rate bound
        if enforce_zlb
            interest_rate_forecast = getindex(D + Z*z_t, ind_r)
            if interest_rate_forecast < zlb_value
                continue_enforce = check_has_unant_mp_sh ? abs.(Z[ind_r, :]' * R[:, ind_r_sh]) > 1e-4 : true

                if continue_enforce
                    # Solve for interest rate shock causing interest rate forecast to be exactly ZLB
                    ϵ_t[ind_r_sh] = 0. # get forecast when MP shock
                    z_t = C + T*z_t1 + R*ϵ_t # is zeroed out
                    z_t_old = C + T*z_t1 + R*ϵ_t
                    ϵ_t[ind_r_sh] = getindex((zlb_value - D[ind_r] - Z[ind_r, :]'*z_t) / (Z[ind_r, :]' * R[:, ind_r_sh]), 1)

                    # Forecast again with new shocks
                    z_t = C + T*z_t1 + R*ϵ_t

                    # Confirm procedure worked
                    interest_rate_forecast = getindex(D + Z*z_t, ind_r)
                    if isnan(interest_rate_forecast)
                        ϵ_t[ind_r_sh] = 0. # get forecast when MP shock
                        z_t = C + T*z_t1 + R*ϵ_t # is zeroed out
                        z_t_old = C + T*z_t1 + R*ϵ_t
                        ϵ_t[ind_r_sh] = getindex((zlb_value - D[ind_r] - Z[ind_r, :]'*z_t) / (Z[ind_r, :]' * R[:, ind_r_sh]), 1)
                    end

                    # Subtract a small number to deal with numerical imprecision
                    @assert interest_rate_forecast >= zlb_value - 0.01 "interest_rate_forecast = $interest_rate_forecast must be >= zlb_value - 0.01 = $(zlb_value - 0.01)."
                end
            end
        end
        return z_t, ϵ_t
    end

    # These are the fcast regime indices
    regime_inds = get_fcast_regime_inds(m, horizon, cond_type)

    # Iterate state space forward
    states = zeros(nstates, horizon)
    obs = zeros(nobs, horizon)
    pseudo = zeros(npseudo, horizon)

    states[:, 1], shocks[:, 1] = iterate(z0, shocks[:, 1], Ts[1], Rs[1], Cs[1], Qs[1], Zs[1], Ds[1])
    obs[:, 1] = Ds[1] .+ Zs[1] * states[:, 1]
    pseudo[:, 1] = D_pseudos[1] .+ Z_pseudos[1] * states[:, 1]

    # If there's multiple regimes in forecast period, go through each set of indices. Otherwise, just take the first set
    if length(regime_inds) > 1
        for i in 1:length(regime_inds)
            ts = regime_inds[i]
            if length(ts) > 0 # Check in case the regime is empty
                if ts[1] == 1 # We already did this period in the initialization
                    ts = 2:ts[end]
                end
            end

            for t in ts
                states[:, t], shocks[:, t] = iterate(states[:, t - 1], shocks[:, t], Ts[i], Rs[i], Cs[i], Qs[i], Zs[i], Ds[i])
                obs[:, t] = Ds[i] .+ Zs[i] * states[:, t]
                pseudo[:, t] = D_pseudos[i] .+ Z_pseudos[i] * states[:, t]
            end
        end
    else
        for t in 2:horizon
            states[:, t], shocks[:, t] = iterate(states[:, t - 1], shocks[:, t], Ts[1], Rs[1], Cs[1], Qs[1], Zs[1], Ds[1])
            obs[:, t] = Ds[1] .+ Zs[1] * states[:, t]
            pseudo[:, t] = D_pseudos[1] .+ Z_pseudos[1] * states[:, t]
        end
    end

    # Return forecasts
    return states, obs, pseudo, shocks
end

# Automatic enforcing of the ZLB as a temporary alternative policy
function forecast(m::AbstractDSGEModel, z0::Vector{S}, states::AbstractMatrix{S},
                  obs::AbstractMatrix{S}, pseudo::AbstractMatrix{S}, shocks::AbstractMatrix{S};
                  cond_type::Symbol = :none, set_zlb_regime_vals::Function = identity,
                  set_info_sets_altpolicy::Function = auto_temp_altpolicy_info_set,
                  update_regime_eqcond_info!::Function =
                  (a, b, c, d) -> default_update_regime_eqcond_info!(a, b, c, d, alternative_policy(m)),
                  tol::S = -1e-5, rerun_smoother::Bool = false,
                  df = nothing, draw_states::Bool = false,
                  nan_failures::Bool = false,
                  histstates::AbstractMatrix{S} = Matrix{S}(undef, 0, 0),
                  histshocks::AbstractMatrix{S} = Matrix{S}(undef, 0, 0),
                  histpseudo::AbstractMatrix{S} = Matrix{S}(undef, 0, 0),
                  initial_states::AbstractVector{S} = Vector{S}(undef, 0)) where {S <: Real}
    # Grab "original" settings" so they can be restored later
    altpol = alternative_policy(m)
    is_regime_switch = haskey(get_settings(m), :regime_switching) ? get_setting(m, :regime_switching) : false
    is_replace_eqcond = haskey(get_settings(m), :replace_eqcond) ? get_setting(m, :replace_eqcond) : false
    is_gensys2 = haskey(get_settings(m), :gensys2) ? get_setting(m, :gensys2) : false
    info_set = haskey(get_settings(m), :tvis_information_set) ? get_setting(m, :tvis_information_set) : UnitRange{Int64}[]
    original_info_set = deepcopy(info_set)
    eqcond_dict = haskey(get_settings(m), :regime_eqcond_info) ? get_setting(m, :regime_eqcond_info) : Dict{Int, EqcondEntry}()
    original_eqcond_dict = deepcopy(eqcond_dict)
    orig_regimes      = haskey(get_settings(m), :n_regimes) ? get_setting(m, :n_regimes) : 1
    regime_dates = haskey(get_settings(m), :regime_dates) ? get_setting(m, :regime_dates) : Dict{Int, Date}()
    orig_regime_dates = deepcopy(regime_dates)
    orig_temp_zlb     = haskey(get_settings(m), :temporary_altpolicy_length) ? get_setting(m, :temporary_altpolicy_length) : nothing
    orig_identical_eqcond_regimes = haskey(get_settings(m), :identical_eqcond_regimes) ? deepcopy(get_setting(m, :identical_eqcond_regimes)) : nothing
    orig_perf_cred_identical_transitions = haskey(get_settings(m), :perfect_credibility_identical_transitions) ? deepcopy(get_setting(m, :perfect_credibility_identical_transitions)) : nothing
    orig_model2para_regime = haskey(get_settings(m), :model2para_regime) ? deepcopy(get_setting(m, :model2para_regime)) : nothing

    # Grab some information about the forecast
    n_hist_regimes = haskey(get_settings(m), :n_hist_regimes) ? get_setting(m, :n_hist_regimes) : 1
    has_reg_dates = haskey(get_settings(m), :regime_dates) # calculate dates of first & last regimes we need to add for the alt policy
    if has_reg_dates && length(get_setting(m, :regime_dates)) > 1
        start_altpol_reg  = get_setting(m, :reg_forecast_start)
        start_altpol_date = get_setting(m, :regime_dates)[start_altpol_reg]
        end_altpol_date   = date_forecast_end(m)
    else
        start_altpol_reg  = n_hist_regimes + 1
        start_altpol_date = date_forecast_start(m)
        end_altpol_date   = iterate_quarters(start_altpol_date, forecast_horizons(m; cond_type = cond_type) - 1)
    end
    altpol_reg_qtrrange   = quarter_range(start_altpol_date, end_altpol_date)
    n_altpol_reg_qtrrange = length(altpol_reg_qtrrange)

    # Determine the number of regimes before the start of the forecast (used to adjust
    # indexing)

    pre_fcast_regimes = n_hist_regimes
    #=
    if cond_type != :none
        if is_regime_switch
            pre_fcast_regimes += get_setting(m, :reg_post_conditional_end) - get_setting(m, :reg_forecast_start)
        else
            pre_fcast_regimes += subtract_quarters(get_setting(m, :date_conditional_end),
                                                   get_setting(m, :date_forecast_start)) + 1
        end
    end
    =#

    # Determine which quarters in the forecast have sub-zlb nominal rates
    has_neg_rates = view(obs, get_observables(m)[:obs_nominalrate], :) .<= forecast_zlb_value(m)

    first_endo_zlb     = findfirst(has_neg_rates)
    # Information set - start of awareness of ZLB
    first_aware = if haskey(get_settings(m), :tvis_information_set)
        findfirst([maximum(info_set[i]) == get_setting(m, :n_regimes) for i in keys(info_set)])
    elseif haskey(get_settings(m), :regime_eqcond_info)
        findfirst([info_set[i].key == :zlb_rule for i in keys(info_set)])
    else
        first_endo_zlb
    end

    ## 1. Determine if we need to do anything (are there any further negative nominal rates)
    # If not, return the forecast as is
    if isnothing(first_endo_zlb)
        if rerun_smoother
            return states, obs, pseudo, histstates, histshocks, histpseudo, initial_states
        else
            return states, obs, pseudo
        end
        # If there are negative nominal rates in the forecast, we continue to computing the endogenous
        # zlb forecast
    else
        min_zlb = haskey(m.settings, :min_temporary_altpolicy_length) ? get_setting(m, :min_temporary_altpolicy_length) : 0
        first_endo_zlb = max(first_endo_zlb, min_zlb + 1)
        last_endo_zlb = findfirst(.!has_neg_rates[first_endo_zlb:end])
        first_endo_zlb += pre_fcast_regimes

        last_endo_zlb = !isnothing(last_endo_zlb) ? last_endo_zlb-1 + first_endo_zlb : size(obs, 2) -3

        max_zlb_regimes = haskey(get_settings(m), :max_temporary_altpolicy_length) ? get_setting(m, :max_temporary_altpolicy_length) : size(obs, 2) - 3
        last_endo_zlb = min(last_endo_zlb, max_zlb_regimes)

        # set up the information sets TODO: add checkfor whether or not we even need to update the tvis_info_set
        if isnothing(first_aware)
            first_aware = get_setting(m, :reg_forecast_start)
        end
        set_info_sets_altpolicy(m, get_setting(m, :n_regimes), first_aware)

        endozlb_forecast::Function = (zlb_start, zlb_end; unant_enforce_zlb = false) -> forecast_endozlb_helper(m, zlb_start, zlb_end, z0, states, shocks,
            orig_regimes, eqcond_dict, regime_dates, info_set;
            cond_type = cond_type, unant_enforce_zlb = unant_enforce_zlb,
            set_zlb_regime_vals = set_zlb_regime_vals,
            set_info_sets_altpolicy = set_info_sets_altpolicy,
            update_regime_eqcond_info! = update_regime_eqcond_info!,
            tol = tol, rerun_smoother = rerun_smoother, df = df,
            draw_states = draw_states, histstates = histstates,
            histshocks = histshocks, histpseudo = histpseudo,
            initial_states = initial_states)
        ## 2. If we don't liftoff post minimum zlb, then enforce the rest of the contiguous zlb
        ##    intervals using two rule (zlb_rule alternative policy)
        if !has_neg_rates[min_zlb+1]
            endo_success = all(obs[get_observables(m)[:obs_nominalrate], :] .> get_setting(m, :zlb_rule_value) / 4. + tol)
            if !endo_success
                system = compute_system(m; tvis = true)
                states, obs, pseudo = forecast(m, system, z0; cond_type = cond_type, shocks = shocks,
                                               enforce_zlb = true)
            end
            if rerun_smoother
                return states, obs, pseudo, histstates, histshocks, histpseudo, initial_states
            else
                return states, obs, pseudo
            end
        else
            # Ensure that the Eqcond Dict is correctly cleared for endo zlb
            temp_altpol_length = 0
            if haskey(m.settings, :regime_eqcond_info)
                for (reg, v) in get_setting(m, :regime_eqcond_info)
                    if v.alternative_policy.key == :zlb_rule || v.alternative_policy.key == :zero_rate
                        if reg >= first_endo_zlb
                            @warn "Regime $reg of regime_eqcond_info used zero rate--to avoid gensys errors in computing the endogenous zlb, this regime is now being set to use $(altpol.key)."
                            v.alternative_policy = altpol
                        else
                            temp_altpol_length += 1
                        end
                    end
                end
            end
            m <= Setting(:temporary_altpolicy_length, temp_altpol_length)

            # Run extended zlb forecast
            states, obs, pseudo, histstates, histshocks, histpseudo, initial_states =
            endozlb_forecast(first_endo_zlb, last_endo_zlb+1, unant_enforce_zlb = false)
            ## 3. Check if this forecast lifts off after the end of the two-rule zlb.
            liftoff = obs[get_observables(m)[:obs_nominalrate], last_endo_zlb-pre_fcast_regimes+1] .> forecast_zlb_value(m)+tol

            ## If it does, binary search between min_zlb and last_endo_zlb to find the earliest liftoff
            if liftoff
                high = last_endo_zlb
                low  = first_endo_zlb
                lookback = haskey(m.settings, :endogenous_zlb_lookback) ? get_setting(m, :endogenous_zlb_lookback)-1 : 2
                iter = max(last_endo_zlb - lookback, first_endo_zlb)

                while true
                    # Run extended zlb forecast
                    states, obs, pseudo, histstates, histshocks, histpseudo, initial_states =
                    endozlb_forecast(first_endo_zlb, iter+1, unant_enforce_zlb = false)
                    ## 3. Check if this forecast lifts off after the end of the two-rule zlb.
                    liftoff = obs[get_observables(m)[:obs_nominalrate], iter-pre_fcast_regimes+1] .> get_setting(m, :zlb_rule_value) / 4. + tol
                    if !liftoff
                        low = iter + 1
                        if iter == last_endo_zlb
                            # We have hit the max number of regimes for this leg of the ZLB, so we've
                            # run Fixed ZLB with unanticipated shocks to enforce ZLB
                            break
                        elseif low-1 == high
                            # We should never reach this point with endo_success false or the
                            # forecast unenforced with unant shocks, but just in case...
                            # (if we do end up here, the forecast will be NaNed)
                            break
                        elseif iter < last_endo_zlb && iter >= last_endo_zlb - lookback
                            # If our initial guess did work but lookback didn't, check ahead
                            # stepwise
                            iter = iter + 1
                        else
                            # Continue Binary Search
                            iter = floor(Int64, (low + high) / 2)
                        end
                    else
                        # If ZLB works (we lift off after the first consecutive stretch)
                        high = iter # Set high to iter so we search for ZLB regimes less than or equal to iter
                        if low == high || iter == low
                            break
                        elseif iter == last_endo_zlb
                            iter = last_endo_zlb - lookback
                        elseif iter == last_endo_zlb - lookback
                            # If first_guess - 2 works, let's try the first possible ZLB regime value
                            iter = first_endo_zlb
                        elseif iter == first_endo_zlb
                            # Minimal number of ZLB works, so we break here
                            # and use iter = first_zlb_regime
                            break
                        elseif iter < last_endo_zlb  && iter > last_endo_zlb - lookback
                            # if we're in this block, last_zlb_reg worked but first_guess -
                            # lookback didn't, so we're proceeding stepwise forward and pick the
                            # first regime that works
                            break
                        else
                            # Continue Binary Search
                            iter = floor(Int64, (low + high) / 2)
                        end
                    end
                end

                last_endo_zlb = iter

                ## If it does not, iterate forward until it does.
            else
                while !liftoff && last_endo_zlb < max_zlb_regimes
                    last_endo_zlb += 1
                    # Run extended zlb forecast
                    states, obs, pseudo, histstates, histshocks, histpseudo, initial_states =
                    endozlb_forecast(first_endo_zlb, last_endo_zlb+1, unant_enforce_zlb = false)
                    ## 3. Check if this forecast lifts off after the end of the two-rule zlb.
                    liftoff = obs[get_observables(m)[:obs_nominalrate], last_endo_zlb-pre_fcast_regimes+1] .> get_setting(m, :zlb_rule_value) / 4. + tol
                end
            end
        end
        ## 6. If we still have further noncontiguous periods of negative rates, run a forecast enforcing zlb over those remaining periods
        ##    using unanticipated monetary policy shocks.
        endo_success = all(obs[get_observables(m)[:obs_nominalrate], :] .> get_setting(m, :zlb_rule_value) / 4. + tol)
        if !endo_success
            states, obs, pseudo, histstates, histshocks, histpseudo, initial_states =
            endozlb_forecast(first_endo_zlb, last_endo_zlb+1, unant_enforce_zlb = true)
        end
        # Restore original settings
        m <= Setting(:regime_switching, is_regime_switch)
        m <= Setting(:regime_dates, orig_regime_dates)
        if !isempty(orig_regime_dates)
            setup_regime_switching_inds!(m; cond_type = cond_type)
        end
        m <= Setting(:replace_eqcond,   is_replace_eqcond)
        m <= Setting(:gensys2,          is_gensys2)
        if !is_replace_eqcond
            delete!(get_settings(m), :regime_eqcond_info)
        end
        if isempty(original_eqcond_dict)
            delete!(get_settings(m), :regime_eqcond_info)
        else
            m <= Setting(:regime_eqcond_info, original_eqcond_dict)
        end
        if haskey(get_settings(m), :tvis_information_set)
            m <= Setting(:tvis_information_set, original_info_set)
        end
        if isnothing(orig_temp_zlb)
            delete!(get_settings(m), :temporary_altpolicy_length)
        else
            m <= Setting(:temporary_altpolicy_length, orig_temp_zlb)
        end
        if isnothing(orig_identical_eqcond_regimes)
            delete!(get_settings(m), :identical_eqcond_regimes)
        else
            m <= Setting(:identical_eqcond_regimes, orig_identical_eqcond_regimes)
        end
        if isnothing(orig_perf_cred_identical_transitions)
            delete!(get_settings(m), :perfect_credibility_identical_transitions)
        else
            m <= Setting(:perfect_credibility_identical_transitions, orig_perf_cred_identical_transitions)
        end
        if isnothing(orig_model2para_regime)
            delete!(get_settings(m), :model2para_regime)
        else
            m <= Setting(:model2para_regime, orig_model2para_regime)
        end
        if rerun_smoother
            return states, obs, pseudo, histstates, histshocks, histpseudo, initial_states
        else
            return states, obs, pseudo
        end
    end
end

function forecast_endozlb_helper(m::AbstractDSGEModel, first_endo_zlb::Int64, liftoff_reg::Int64,
                                 z0::Vector{S}, states::AbstractMatrix{S}, shocks::AbstractMatrix{S},
                                 orig_regimes::Int64, original_eqcond_dict::Dict{Int64, EqcondEntry},
                                 orig_regime_dates::Dict{Int, Date}, original_info_set::Union{UnitRange{Int64}, Array{UnitRange{Int64}}};
                                 unant_enforce_zlb::Bool = false, cond_type::Symbol = :none, set_zlb_regime_vals::Function = identity,
                                 set_info_sets_altpolicy::Function = auto_temp_altpolicy_info_set,
                                 update_regime_eqcond_info!::Function =
                                 (a, b, c, d) -> default_update_regime_eqcond_info!(a, b, c, d, alternative_policy(m)),
                                 tol::S = -1e-5, rerun_smoother::Bool = false,
                                 df = nothing, draw_states::Bool = false,
                                 histstates::AbstractMatrix{S} = Matrix{S}(undef, 0, 0),
                                 histshocks::AbstractMatrix{S} = Matrix{S}(undef, 0, 0),
                                 histpseudo::AbstractMatrix{S} = Matrix{S}(undef, 0, 0),
                                 initial_states::AbstractVector{S} = Vector{S}(undef, 0)) where {S <: Real}

    altpol = alternative_policy(m)
    is_regime_switch = haskey(get_settings(m), :regime_switching) ? get_setting(m, :regime_switching) : false
    is_replace_eqcond = haskey(get_settings(m), :replace_eqcond) ? get_setting(m, :replace_eqcond) : false
    is_gensys2 = haskey(get_settings(m), :gensys2) ? get_setting(m, :gensys2) : false
    # Grab some information about the forecast
    n_hist_regimes = haskey(get_settings(m), :n_hist_regimes) ? get_setting(m, :n_hist_regimes) : 1
    has_reg_dates = haskey(get_settings(m), :regime_dates) # calculate dates of first & last regimes we need to add for the alt policy
    if has_reg_dates && length(get_setting(m, :regime_dates)) > 1
        start_altpol_reg  = get_setting(m, :reg_forecast_start)
        start_altpol_date = get_setting(m, :regime_dates)[start_altpol_reg]
        end_altpol_date   = date_forecast_end(m)
    else
        start_altpol_reg  = n_hist_regimes + 1
        start_altpol_date = date_forecast_start(m)
        end_altpol_date   = iterate_quarters(start_altpol_date, forecast_horizons(m; cond_type = cond_type) - 1)
    end
    altpol_reg_qtrrange   = quarter_range(start_altpol_date, end_altpol_date)
    n_altpol_reg_qtrrange = length(altpol_reg_qtrrange)
    orig_temp_zlb     = haskey(get_settings(m), :temporary_altpolicy_length) ? get_setting(m, :temporary_altpolicy_length) : nothing

    # Set up regime dates
    altpol_regime_dates = Dict{Int, Date}(1 => date_presample_start(m))
    if is_regime_switch # Add historical regimes
        for regind in 2:n_hist_regimes
            altpol_regime_dates[regind] = orig_regime_dates[regind]
        end
    end

    altpol_reg_range = start_altpol_reg:liftoff_reg
    for (regind, date) in zip(altpol_reg_range, altpol_reg_qtrrange)
        altpol_regime_dates[regind] = date
    end

    # in the case where we're shortening the zlb, this is necessary to stop the
    # setup_regime_switching_inds! call from truncating on a zlb reg and throwing
    # a gensys error
    if haskey(get_settings(m), :regime_eqcond_info) ? haskey(get_setting(m, :regime_eqcond_info), liftoff_reg) : false
        get_setting(m, :regime_eqcond_info)[liftoff_reg].alternative_policy = alternative_policy(m)
    end

    if haskey(get_settings(m), :cred_vary_until) ? (isempty(altpol_reg_range) ? true : get_setting(m, :cred_vary_until) >= maximum(altpol_reg_range)) : false
        m <= Setting(:regime_switching, true)
    else
        # NOTE: the following will truncate regime_eqcond_info after liftoff regime unless cred_vary_until
        # is set. At present, this DOES NOT HANDLE cases where there are more policy changes after the
        # liftoff regime. If this is the case, use cred_vary_until up to the end of those changes.
        if haskey(get_settings(m), :regime_eqcond_info)
            for i in keys(get_setting(m, :regime_eqcond_info))
                if i > liftoff_reg
                    delete!(get_setting(m, :regime_eqcond_info), i)
                end
            end
        end
        m <= Setting(:regime_dates, altpol_regime_dates)
        m <= Setting(:regime_switching, true)
        setup_regime_switching_inds!(m; cond_type = cond_type)
    end

    # Set up replace_eqcond entries
    m <= Setting(:replace_eqcond, true)
    m <= Setting(:gensys2, true)

    # In general, the update_regime_eqcond_info! function should not change the
    # EqcondEntry during historical/conditional horizon regimes, but any other
    # regimes in the forecast horizon should be/can be set.
    #
    # From above, we also assume that no ZLBs occur in the eqcond_dict that
    # is passed to update_regime_eqcond_info! and assume that
    # if there is a particular sequence of alternative policies the user
    # wants to occur after the ZLB, then the user will have incorporated
    # code that implements that sequence in their own update_regime_eqcond_info!
    # function. In the default DSGE policy, the regimes after the ZLB ends
    # are updated only if there is time-varying credibility
    # (specified by the Setting :cred_vary_until).
    update_regime_eqcond_info!(m, original_eqcond_dict, first_endo_zlb, liftoff_reg)
    min_zlb = haskey(m.settings, :min_temporary_altpolicy_length) ? get_setting(m, :min_temporary_altpolicy_length) : 0
    min_zlb += haskey(m.settings, :historical_temporary_altpolicy_length) ? get_setting(m, :historical_temporary_altpolicy_length) : 0
    m <= Setting(:temporary_altpolicy_length, min_zlb + liftoff_reg-first_endo_zlb)

    # Set up parameters if there are switching parameter values.
    #
    # User needs to provide a function which takes in the model object `m`
    # and the total number of regimes (after adding the required temporary regimes),
    # and sets up regime-switching parameters for these new additional regimes.
    if set_zlb_regime_vals != identity
        set_zlb_regime_vals(m, liftoff_reg)
    end
    # if we have a mapping between model regimes and parameter regimes, make sure to
    # extend that mapping if we've added regimes in the process of running the endo zlb
    if haskey(m.settings, :model2para_regime)
        for para in m.parameters
            model2para_dict = get_setting(m, :model2para_regime)
            if haskey(model2para_dict, para.key)
                for i in first_endo_zlb:liftoff_reg
                    if !haskey(model2para_dict[para.key], i)
                        model2para_dic[para.key][i] = model2para_dict[para.key][i-1]
                    end
                end
            end
        end
    end

    # set up the information sets TODO: add checkfor whether or not we even need to update the tvis_info_set
    first_aware = if haskey(get_settings(m), :tvis_information_set)
        findfirst([maximum(original_info_set[i]) == get_setting(m, :n_regimes) for i in keys(original_info_set)])
    elseif haskey(get_settings(m), :regime_eqcond_info)
        findfirst([original_info_set[i].key == :zlb_rule for i in keys(original_info_set)])
    else
        nothing
    end
    if isnothing(first_aware)
        first_aware = first_endo_zlb
    end
    set_info_sets_altpolicy(m, get_setting(m, :n_regimes), first_aware)

    # TODO: maybe delete tvis, since tvis should always be on given the fact we always run set_info_sets
    # tvis = haskey(get_settings(m), :tvis_information_set) && !isempty(get_setting(m, :tvis_information_set))

    # Adjust the identical perfect credibility transitions for the new zlb
    # Note: the following assumes that there are no more policy changes after the switch to flexible ait at
    # the end of the zlb, and that all zlb regimes enforced with eqcond are consecutive.
    # If either of these are no longer true, this will break!
    if haskey(m.settings, :perfect_credibility_identical_transitions)
        for reg in first_endo_zlb:get_setting(m, :n_regimes)
            delete!(get_setting(m, :perfect_credibility_identical_transitions), reg)
        end
        for reg in liftoff_reg+2:get_setting(m, :n_regimes)
            get_setting(m, :perfect_credibility_identical_transitions)[reg] = liftoff_reg+1
        end
    end
    if haskey(m.settings, :identical_eqcond_regimes)
        for reg in first_endo_zlb:get_setting(m, :n_regimes)
            delete!(get_setting(m, :identical_eqcond_regimes), reg)
        end
        first_zlb_reg = minimum(collect(keys(get_setting(m, :regime_eqcond_info))))
        for reg in first_zlb_reg+1:liftoff_reg-1
            get_setting(m, :identical_eqcond_regimes)[reg] = first_zlb_reg
        end
        for reg in liftoff_reg+2:get_setting(m, :n_regimes)
            get_setting(m, :identical_eqcond_regimes)[reg] = liftoff_reg+1
        end
    end

    # Recompute to account for new regimes
    system = compute_system(m; tvis = true)
    if rerun_smoother # if state space system changes, then the smoothed states will also change generally
        histstates, histshocks, histpseudo, initial_states =
        smooth(m, df, system; cond_type = cond_type, draw_states = draw_states)
        z0 = histstates[:, end]
    end

    # Forecast! Note that since forecast(m, system, z0) allocates new matrices for states, obs, and pseudo,
    # this step merely changes to which matrices the local variables states, obs, and pseudo point
    # w/in this function's closure. Thus, the matrices states, obs, and pseudo which are first
    # passed into this function will not be over-written.
    states, obs, pseudo = forecast(m, system, z0; cond_type = cond_type, shocks = shocks,
                                   enforce_zlb = unant_enforce_zlb)

    # Delete extra regimes added to implement the temporary alternative policy, or else updating the parameters
    # in forecast_one will not work.
    if set_zlb_regime_vals != identity
        for p in m.parameters
            if haskey(p.regimes, :value)
                if length(p.regimes[:value]) > orig_regimes
                    for i in (orig_regimes + 1):length(p.regimes[:value])
                        delete!(p.regimes[:value], i)
                    end
                end
            end
        end
    end

    return states, obs, pseudo, histstates, histshocks, histpseudo, initial_states
end

function get_fcast_regime_inds(m::AbstractDSGEModel, horizon::Int, cond_type::Symbol;
                               start_index::Int = 0)

    fcast_post_cond_date = (cond_type == :none) ? date_forecast_start(m) : # If cond forecast, then we want to start forecasting
    max(date_forecast_start(m), iterate_quarters(date_conditional_end(m), 1)) # from the period after the conditional end period
    # last_date = iterate_quarters(fcast_post_cond_date, -1) # minus 1 to get one date before the forecast start
    last_date = fcast_post_cond_date
    last_ind = 1

    # Construct vector of time periods for each regime in the forecast periods after the conditional forecast
    start_reg   = (cond_type == :none) ? get_setting(m, :reg_forecast_start) :
    max(get_setting(m, :reg_forecast_start), get_setting(m, :reg_post_conditional_end))
    regime_inds = Vector{UnitRange{Int}}(undef, get_setting(m, :n_regimes) - start_reg + 1)
    for (rgi, i) in enumerate(start_reg:(get_setting(m, :n_regimes) - 1)) # Last regime handled separately
        qtr_diff = subtract_quarters(get_setting(m, :regime_dates)[i + 1], last_date)
        regime_inds[rgi] = (start_index + last_ind):(start_index + last_ind + qtr_diff - 1)
        last_ind += qtr_diff
        last_date = get_setting(m, :regime_dates)[i + 1]
    end
    regime_inds[end] = (start_index + last_ind):(start_index + horizon) # Covers case where final hist regime is also first (and only) forecast regime.

    return regime_inds
end

function auto_temp_altpolicy_info_set(m::AbstractDSGEModel, n_regimes::Int, first_zlb_regime::Int)
    tvis_infoset = Vector{UnitRange{Int}}(undef, n_regimes)
    for r in 1:n_regimes
        if r < first_zlb_regime
            tvis_infoset[r] = r:r
        else
            tvis_infoset[r] = r:n_regimes
        end
    end
    m <= Setting(:tvis_information_set, tvis_infoset)
end

"""
```
default_update_regime_eqcond_info!(m::AbstractDSGEModel, eqcond_dict::AbstractDict{Int64, EqcondEntry},
                                   zlb_start_regime::Int64, liftoff_regime::Int64, liftoff_policy::AltPolicy)
```
is the default method for updating the setting `regime_eqcond_info` during the automatic
endogenous ZLB enforcement as a temporary policy.

The input `eqcond_dict` should be a dictionary whose historical/conditional horizon regimes
should already be set (and won't be affected by this function.
The regimes which will be altered are those for which the temporary ZLB will apply
(according to `zlb_start_regime` and `liftoff_regime`), and if there is
time-varying credibility, forecast regimes past the ZLB.

In general, other implementations of the `update_regime_eqcond_info!` function should not change the
EqcondEntry during historical/conditional horizon regimes, but any other
regimes in the forecast horizon should be/can be set.
"""
function default_update_regime_eqcond_info!(m::AbstractDSGEModel, eqcond_dict::AbstractDict{Int64, EqcondEntry},
                                            zlb_start_regime::Int64, liftoff_regime::Int64, liftoff_policy::AltPolicy)

    # TODO: add a check for making sure weights are properly set (e.g. enough regimes with weights to avoid error)
    # Populate eqcond_dict for regimes in which the ZLB should apply and update the weights as appropriate
    for regind in zlb_start_regime:(liftoff_regime - 1)
        if haskey(eqcond_dict, regind)
            # If regime index exists already, just directly change the
            # alternative_policy (which leaves the credibility weights unchanged)
            eqcond_dict[regind].alternative_policy = zlb_rule() # Temp ZLB rule in this regimes
        else
            # If the regime index doesn't already exist, then add an EqcondEntry
            # for a ZLB that is perfectly credible
            weights = zeros(haskey(get_settings(m), :alternative_policies) ? length(get_setting(m, :alternative_policies)) + 1 : 1)
            weights[1] = 1.
            eqcond_dict[regind] = EqcondEntry(zlb_rule(), weights)
        end
    end

    # Set the liftoff regime's EqcondEntry, using same logic as above
    if haskey(eqcond_dict, liftoff_regime)
        eqcond_dict[liftoff_regime].alternative_policy = liftoff_policy
    else
        weights = zeros(haskey(m.settings, :alternative_policies) ? length(get_setting(m, :alternative_policies)) + 1 : 1)
        weights[1] = 1.
        eqcond_dict[liftoff_regime] = EqcondEntry(liftoff_policy, weights)
    end

    # NOTE: the following block assumes there is only one temporary altpol
    # Update eqcond_dict to use the lift-off policy for any regimes between (inclusive)
    # the liftoff regime and the first regime for which credibility stops varying.
    if haskey(get_settings(m), :cred_vary_until) && get_setting(m, :cred_vary_until) >= liftoff_regime
        for z in liftoff_regime:(get_setting(m, :cred_vary_until))
            eqcond_dict[z].alternative_policy = liftoff_policy
        end
    end

    m <= Setting(:regime_eqcond_info, eqcond_dict)
    m <= Setting(:n_regimes, maximum(collect(keys(eqcond_dict))))

    return m
end


text:
push!(LOAD_PATH, "../src/")
using HiddenMarkovModelReaders
using Documenter

using HiddenMarkovModelReaders;
DocMeta.setdocmeta!(HiddenMarkovModelReaders, :DocTestSetup, :(using HiddenMarkovModelReaders); recursive = true);
doctest(HiddenMarkovModelReaders);

makedocs(;
    modules = [HiddenMarkovModelReaders],
    authors = "DanielRivasMD <danielrivasmd@gmail.com> and contributors",
    repo = "https://github.com/DanielRivasMD/HiddenMarkovModelReaders/blob/{commit}{path}#{line}",
    sitename = "HiddenMarkovModelReaders",
    format = Documenter.HTML(;
        prettyurls = get(ENV, "CI", "false") == "true",
        canonical = "https://DanielRivasMD.github.io/HiddenMarkovModelReaders",
        assets = String[]
    ),
    pages = [
        "Home" => "index.md",
    ]
)

deploydocs(;
    repo = "github.com/DanielRivasMD/HiddenMarkovModelReaders"
)

ext: jl


text:
using CFLPLib
using Documenter

DocMeta.setdocmeta!(CFLPLib, :DocTestSetup, :(using CFLPLib); recursive=true)

makedocs(;
    modules=[CFLPLib],
    authors="Rafael Martinelli",
    repo="https://github.com/rafaelmartinelli/CFLPLib.jl/blob/{commit}{path}#{line}",
    sitename="CFLPLib.jl",
    format=Documenter.HTML(;
        prettyurls=get(ENV, "CI", "false") == "true",
        canonical="https://rafaelmartinelli.github.io/CFLPLib.jl",
        assets=String[],
    ),
    pages=[
        "Home" => "index.md",
    ],
)

deploydocs(;
    repo="github.com/rafaelmartinelli/CFLPLib.jl",
)


text:
# Julia wrapper for header: /opt/ros/crystal/include/rcl/allocator.h
# Automatically generated using Clang.jl wrap_c

# Julia wrapper for header: /opt/ros/crystal/include/rcl/arguments.h
# Automatically generated using Clang.jl wrap_c

function rcl_get_zero_initialized_arguments()
    ccall((:rcl_get_zero_initialized_arguments, librcl), rcl_arguments_t, ())
end

function rcl_parse_arguments(argc, argv, allocator, args_output)
    ccall((:rcl_parse_arguments, librcl), rcl_ret_t, (Cint, Ptr{Cstring}, rcl_allocator_t, Ptr{rcl_arguments_t}), argc, argv, allocator, args_output)
end

function rcl_arguments_get_count_unparsed(args)
    ccall((:rcl_arguments_get_count_unparsed, librcl), Cint, (Ptr{rcl_arguments_t},), args)
end

function rcl_arguments_get_unparsed(args, allocator, output_unparsed_indices)
    ccall((:rcl_arguments_get_unparsed, librcl), rcl_ret_t, (Ptr{rcl_arguments_t}, rcl_allocator_t, Ptr{Ptr{Cint}}), args, allocator, output_unparsed_indices)
end

function rcl_arguments_get_param_files_count(args)
    ccall((:rcl_arguments_get_param_files_count, librcl), Cint, (Ptr{rcl_arguments_t},), args)
end

function rcl_arguments_get_param_files(arguments, allocator, parameter_files)
    ccall((:rcl_arguments_get_param_files, librcl), rcl_ret_t, (Ptr{rcl_arguments_t}, rcl_allocator_t, Ptr{Ptr{Cstring}}), arguments, allocator, parameter_files)
end

function rcl_remove_ros_arguments(argv, args, allocator, nonros_argc, nonros_argv)
    ccall((:rcl_remove_ros_arguments, librcl), rcl_ret_t, (Ptr{Cstring}, Ptr{rcl_arguments_t}, rcl_allocator_t, Ptr{Cint}, Ptr{Ptr{Cstring}}), argv, args, allocator, nonros_argc, nonros_argv)
end

function rcl_arguments_copy(args, args_out)
    ccall((:rcl_arguments_copy, librcl), rcl_ret_t, (Ptr{rcl_arguments_t}, Ptr{rcl_arguments_t}), args, args_out)
end

function rcl_arguments_fini(args)
    ccall((:rcl_arguments_fini, librcl), rcl_ret_t, (Ptr{rcl_arguments_t},), args)
end
# Julia wrapper for header: /opt/ros/crystal/include/rcl/client.h
# Automatically generated using Clang.jl wrap_c

function rcl_get_zero_initialized_client()
    ccall((:rcl_get_zero_initialized_client, librcl), rcl_client_t, ())
end

function rcl_client_init(client, node, type_support, service_name, options)
    ccall((:rcl_client_init, librcl), rcl_ret_t, (Ptr{rcl_client_t}, Ptr{rcl_node_t}, Ptr{rosidl_service_type_support_t}, Cstring, Ptr{rcl_client_options_t}), client, node, type_support, service_name, options)
end

function rcl_client_fini(client, node)
    ccall((:rcl_client_fini, librcl), rcl_ret_t, (Ptr{rcl_client_t}, Ptr{rcl_node_t}), client, node)
end

function rcl_client_get_default_options()
    ccall((:rcl_client_get_default_options, librcl), rcl_client_options_t, ())
end

function rcl_send_request(client, ros_request, sequence_number)
    ccall((:rcl_send_request, librcl), rcl_ret_t, (Ptr{rcl_client_t}, Ptr{Cvoid}, Ptr{Int64}), client, ros_request, sequence_number)
end

function rcl_take_response(client, request_header, ros_response)
    ccall((:rcl_take_response, librcl), rcl_ret_t, (Ptr{rcl_client_t}, Ptr{rmw_request_id_t}, Ptr{Cvoid}), client, request_header, ros_response)
end

function rcl_client_get_service_name(client)
    ccall((:rcl_client_get_service_name, librcl), Cstring, (Ptr{rcl_client_t},), client)
end

function rcl_client_get_options(client)
    ccall((:rcl_client_get_options, librcl), Ptr{rcl_client_options_t}, (Ptr{rcl_client_t},), client)
end

function rcl_client_get_rmw_handle(client)
    ccall((:rcl_client_get_rmw_handle, librcl), Ptr{rmw_client_t}, (Ptr{rcl_client_t},), client)
end

function rcl_client_is_valid(client)
    ccall((:rcl_client_is_valid, librcl), Bool, (Ptr{rcl_client_t},), client)
end
# Julia wrapper for header: /opt/ros/crystal/include/rcl/context.h
# Automatically generated using Clang.jl wrap_c

function rcl_get_zero_initialized_context()
    ccall((:rcl_get_zero_initialized_context, librcl), rcl_context_t, ())
end

function rcl_context_fini(context)
    ccall((:rcl_context_fini, librcl), rcl_ret_t, (Ptr{rcl_context_t},), context)
end

function rcl_context_get_init_options(context)
    ccall((:rcl_context_get_init_options, librcl), Ptr{rcl_init_options_t}, (Ptr{rcl_context_t},), context)
end

function rcl_context_get_instance_id(context)
    ccall((:rcl_context_get_instance_id, librcl), rcl_context_instance_id_t, (Ptr{rcl_context_t},), context)
end

function rcl_context_is_valid(context)
    ccall((:rcl_context_is_valid, librcl), Bool, (Ptr{rcl_context_t},), context)
end

function rcl_context_get_rmw_context(context)
    ccall((:rcl_context_get_rmw_context, librcl), Ptr{rmw_context_t}, (Ptr{rcl_context_t},), context)
end
# Julia wrapper for header: /opt/ros/crystal/include/rcl/error_handling.h
# Automatically generated using Clang.jl wrap_c

# Julia wrapper for header: /opt/ros/crystal/include/rcl/expand_topic_name.h
# Automatically generated using Clang.jl wrap_c

function rcl_expand_topic_name(input_topic_name, node_name, node_namespace, substitutions, allocator, output_topic_name)
    ccall((:rcl_expand_topic_name, librcl), rcl_ret_t, (Cstring, Cstring, Cstring, Ptr{rcutils_string_map_t}, rcl_allocator_t, Ptr{Cstring}), input_topic_name, node_name, node_namespace, substitutions, allocator, output_topic_name)
end

function rcl_get_default_topic_name_substitutions(string_map)
    ccall((:rcl_get_default_topic_name_substitutions, librcl), rcl_ret_t, (Ptr{rcutils_string_map_t},), string_map)
end
# Julia wrapper for header: /opt/ros/crystal/include/rcl/graph.h
# Automatically generated using Clang.jl wrap_c

function rcl_get_publisher_names_and_types_by_node(node, allocator, no_demangle, node_name, node_namespace, topic_names_and_types)
    ccall((:rcl_get_publisher_names_and_types_by_node, librcl), rcl_ret_t, (Ptr{rcl_node_t}, Ptr{rcl_allocator_t}, Bool, Cstring, Cstring, Ptr{rcl_names_and_types_t}), node, allocator, no_demangle, node_name, node_namespace, topic_names_and_types)
end

function rcl_get_subscriber_names_and_types_by_node(node, allocator, no_demangle, node_name, node_namespace, topic_names_and_types)
    ccall((:rcl_get_subscriber_names_and_types_by_node, librcl), rcl_ret_t, (Ptr{rcl_node_t}, Ptr{rcl_allocator_t}, Bool, Cstring, Cstring, Ptr{rcl_names_and_types_t}), node, allocator, no_demangle, node_name, node_namespace, topic_names_and_types)
end

function rcl_get_service_names_and_types_by_node(node, allocator, node_name, node_namespace, service_names_and_types)
    ccall((:rcl_get_service_names_and_types_by_node, librcl), rcl_ret_t, (Ptr{rcl_node_t}, Ptr{rcl_allocator_t}, Cstring, Cstring, Ptr{rcl_names_and_types_t}), node, allocator, node_name, node_namespace, service_names_and_types)
end

function rcl_get_topic_names_and_types(node, allocator, no_demangle, topic_names_and_types)
    ccall((:rcl_get_topic_names_and_types, librcl), rcl_ret_t, (Ptr{rcl_node_t}, Ptr{rcl_allocator_t}, Bool, Ptr{rcl_names_and_types_t}), node, allocator, no_demangle, topic_names_and_types)
end

function rcl_get_service_names_and_types(node, allocator, service_names_and_types)
    ccall((:rcl_get_service_names_and_types, librcl), rcl_ret_t, (Ptr{rcl_node_t}, Ptr{rcl_allocator_t}, Ptr{rcl_names_and_types_t}), node, allocator, service_names_and_types)
end

function rcl_names_and_types_fini(names_and_types)
    ccall((:rcl_names_and_types_fini, librcl), rcl_ret_t, (Ptr{rcl_names_and_types_t},), names_and_types)
end

function rcl_get_node_names(node, allocator, node_names, node_namespaces)
    ccall((:rcl_get_node_names, librcl), rcl_ret_t, (Ptr{rcl_node_t}, rcl_allocator_t, Ptr{rcutils_string_array_t}, Ptr{rcutils_string_array_t}), node, allocator, node_names, node_namespaces)
end

function rcl_count_publishers(node, topic_name, count)
    ccall((:rcl_count_publishers, librcl), rcl_ret_t, (Ptr{rcl_node_t}, Cstring, Ptr{Csize_t}), node, topic_name, count)
end

function rcl_count_subscribers(node, topic_name, count)
    ccall((:rcl_count_subscribers, librcl), rcl_ret_t, (Ptr{rcl_node_t}, Cstring, Ptr{Csize_t}), node, topic_name, count)
end

function rcl_service_server_is_available(node, client, is_available)
    ccall((:rcl_service_server_is_available, librcl), rcl_ret_t, (Ptr{rcl_node_t}, Ptr{rcl_client_t}, Ptr{Bool}), node, client, is_available)
end
# Julia wrapper for header: /opt/ros/crystal/include/rcl/guard_condition.h
# Automatically generated using Clang.jl wrap_c

function rcl_get_zero_initialized_guard_condition()
    ccall((:rcl_get_zero_initialized_guard_condition, librcl), rcl_guard_condition_t, ())
end

function rcl_guard_condition_init(guard_condition, context, options)
    ccall((:rcl_guard_condition_init, librcl), rcl_ret_t, (Ptr{rcl_guard_condition_t}, Ptr{rcl_context_t}, rcl_guard_condition_options_t), guard_condition, context, options)
end

function rcl_guard_condition_init_from_rmw(guard_condition, rmw_guard_condition, context, options)
    ccall((:rcl_guard_condition_init_from_rmw, librcl), rcl_ret_t, (Ptr{rcl_guard_condition_t}, Ptr{rmw_guard_condition_t}, Ptr{rcl_context_t}, rcl_guard_condition_options_t), guard_condition, rmw_guard_condition, context, options)
end

function rcl_guard_condition_fini(guard_condition)
    ccall((:rcl_guard_condition_fini, librcl), rcl_ret_t, (Ptr{rcl_guard_condition_t},), guard_condition)
end

function rcl_guard_condition_get_default_options()
    ccall((:rcl_guard_condition_get_default_options, librcl), rcl_guard_condition_options_t, ())
end

function rcl_trigger_guard_condition(guard_condition)
    ccall((:rcl_trigger_guard_condition, librcl), rcl_ret_t, (Ptr{rcl_guard_condition_t},), guard_condition)
end

function rcl_guard_condition_get_options(guard_condition)
    ccall((:rcl_guard_condition_get_options, librcl), Ptr{rcl_guard_condition_options_t}, (Ptr{rcl_guard_condition_t},), guard_condition)
end

function rcl_guard_condition_get_rmw_handle(guard_condition)
    ccall((:rcl_guard_condition_get_rmw_handle, librcl), Ptr{rmw_guard_condition_t}, (Ptr{rcl_guard_condition_t},), guard_condition)
end
# Julia wrapper for header: /opt/ros/crystal/include/rcl/init.h
# Automatically generated using Clang.jl wrap_c

function rcl_init(argc, argv, options, context)
    ccall((:rcl_init, librcl), rcl_ret_t, (Cint, Ptr{Cstring}, Ptr{rcl_init_options_t}, Ptr{rcl_context_t}), argc, argv, options, context)
end

function rcl_shutdown(context)
    ccall((:rcl_shutdown, librcl), rcl_ret_t, (Ptr{rcl_context_t},), context)
end
# Julia wrapper for header: /opt/ros/crystal/include/rcl/init_options.h
# Automatically generated using Clang.jl wrap_c

function rcl_get_zero_initialized_init_options()
    ccall((:rcl_get_zero_initialized_init_options, librcl), rcl_init_options_t, ())
end

function rcl_init_options_init(init_options, allocator)
    ccall((:rcl_init_options_init, librcl), rcl_ret_t, (Ptr{rcl_init_options_t}, rcl_allocator_t), init_options, allocator)
end

function rcl_init_options_copy(src, dst)
    ccall((:rcl_init_options_copy, librcl), rcl_ret_t, (Ptr{rcl_init_options_t}, Ptr{rcl_init_options_t}), src, dst)
end

function rcl_init_options_fini(init_options)
    ccall((:rcl_init_options_fini, librcl), rcl_ret_t, (Ptr{rcl_init_options_t},), init_options)
end

function rcl_init_options_get_rmw_init_options(init_options)
    ccall((:rcl_init_options_get_rmw_init_options, librcl), Ptr{rmw_init_options_t}, (Ptr{rcl_init_options_t},), init_options)
end
# Julia wrapper for header: /opt/ros/crystal/include/rcl/lexer.h
# Automatically generated using Clang.jl wrap_c

function rcl_lexer_analyze(text, lexeme, length)
    ccall((:rcl_lexer_analyze, librcl), rcl_ret_t, (Cstring, Ptr{rcl_lexeme_t}, Ptr{Csize_t}), text, lexeme, length)
end
# Julia wrapper for header: /opt/ros/crystal/include/rcl/lexer_lookahead.h
# Automatically generated using Clang.jl wrap_c

function rcl_get_zero_initialized_lexer_lookahead2()
    ccall((:rcl_get_zero_initialized_lexer_lookahead2, librcl), rcl_lexer_lookahead2_t, ())
end

function rcl_lexer_lookahead2_init(buffer, text, allocator)
    ccall((:rcl_lexer_lookahead2_init, librcl), rcl_ret_t, (Ptr{rcl_lexer_lookahead2_t}, Cstring, rcl_allocator_t), buffer, text, allocator)
end

function rcl_lexer_lookahead2_fini(buffer)
    ccall((:rcl_lexer_lookahead2_fini, librcl), rcl_ret_t, (Ptr{rcl_lexer_lookahead2_t},), buffer)
end

function rcl_lexer_lookahead2_peek(buffer, next_type)
    ccall((:rcl_lexer_lookahead2_peek, librcl), rcl_ret_t, (Ptr{rcl_lexer_lookahead2_t}, Ptr{rcl_lexeme_t}), buffer, next_type)
end

function rcl_lexer_lookahead2_peek2(buffer, next_type1, next_type2)
    ccall((:rcl_lexer_lookahead2_peek2, librcl), rcl_ret_t, (Ptr{rcl_lexer_lookahead2_t}, Ptr{rcl_lexeme_t}, Ptr{rcl_lexeme_t}), buffer, next_type1, next_type2)
end

function rcl_lexer_lookahead2_accept(buffer, lexeme_text, lexeme_text_length)
    ccall((:rcl_lexer_lookahead2_accept, librcl), rcl_ret_t, (Ptr{rcl_lexer_lookahead2_t}, Ptr{Cstring}, Ptr{Csize_t}), buffer, lexeme_text, lexeme_text_length)
end

function rcl_lexer_lookahead2_expect(buffer, type, lexeme_text, lexeme_text_length)
    ccall((:rcl_lexer_lookahead2_expect, librcl), rcl_ret_t, (Ptr{rcl_lexer_lookahead2_t}, rcl_lexeme_t, Ptr{Cstring}, Ptr{Csize_t}), buffer, type, lexeme_text, lexeme_text_length)
end

function rcl_lexer_lookahead2_get_text(buffer)
    ccall((:rcl_lexer_lookahead2_get_text, librcl), Cstring, (Ptr{rcl_lexer_lookahead2_t},), buffer)
end
# Julia wrapper for header: /opt/ros/crystal/include/rcl/logging.h
# Automatically generated using Clang.jl wrap_c

function rcl_logging_configure(global_args, allocator)
    ccall((:rcl_logging_configure, librcl), rcl_ret_t, (Ptr{rcl_arguments_t}, Ptr{rcl_allocator_t}), global_args, allocator)
end

function rcl_logging_fini()
    ccall((:rcl_logging_fini, librcl), rcl_ret_t, ())
end
# Julia wrapper for header: /opt/ros/crystal/include/rcl/logging_external_interface.h
# Automatically generated using Clang.jl wrap_c

function rcl_logging_external_initialize(config_file)
    ccall((:rcl_logging_external_initialize, librcl), rcl_ret_t, (Cstring,), config_file)
end

function rcl_logging_external_shutdown()
    ccall((:rcl_logging_external_shutdown, librcl), rcl_ret_t, ())
end

function rcl_logging_external_log(severity, name, msg)
    ccall((:rcl_logging_external_log, librcl), Cvoid, (Cint, Cstring, Cstring), severity, name, msg)
end

function rcl_logging_external_set_logger_level(name, level)
    ccall((:rcl_logging_external_set_logger_level, librcl), rcl_ret_t, (Cstring, Cint), name, level)
end
# Julia wrapper for header: /opt/ros/crystal/include/rcl/logging_rosout.h
# Automatically generated using Clang.jl wrap_c

function rcl_logging_rosout_init(allocator)
    ccall((:rcl_logging_rosout_init, librcl), rcl_ret_t, (Ptr{rcl_allocator_t},), allocator)
end

function rcl_logging_rosout_fini()
    ccall((:rcl_logging_rosout_fini, librcl), rcl_ret_t, ())
end

function rcl_logging_rosout_init_publisher_for_node(node)
    ccall((:rcl_logging_rosout_init_publisher_for_node, librcl), rcl_ret_t, (Ptr{rcl_node_t},), node)
end

function rcl_logging_rosout_fini_publisher_for_node(node)
    ccall((:rcl_logging_rosout_fini_publisher_for_node, librcl), rcl_ret_t, (Ptr{rcl_node_t},), node)
end

# HACK: comment out because Clang.jl doesn't support va_list
# function rcl_logging_rosout_output_handler(location, severity, name, timestamp, format, args)
#     ccall((:rcl_logging_rosout_output_handler, librcl), Cvoid, (Ptr{rcutils_log_location_t}, Cint, Cstring, rcutils_time_point_value_t, Cstring, Ptr{va_list}), location, severity, name, timestamp, format, args)
# end
# Julia wrapper for header: /opt/ros/crystal/include/rcl/macros.h
# Automatically generated using Clang.jl wrap_c

# Julia wrapper for header: /opt/ros/crystal/include/rcl/node.h
# Automatically generated using Clang.jl wrap_c

function rcl_node_get_default_options()
    ccall((:rcl_node_get_default_options, librcl), rcl_node_options_t, ())
end

function rcl_get_zero_initialized_node()
    ccall((:rcl_get_zero_initialized_node, librcl), rcl_node_t, ())
end

function rcl_node_init(node, name, namespace_, context, options)
    ccall((:rcl_node_init, librcl), rcl_ret_t, (Ptr{rcl_node_t}, Cstring, Cstring, Ptr{rcl_context_t}, Ptr{rcl_node_options_t}), node, name, namespace_, context, options)
end

function rcl_node_fini(node)
    ccall((:rcl_node_fini, librcl), rcl_ret_t, (Ptr{rcl_node_t},), node)
end

function rcl_node_options_copy(options, options_out)
    ccall((:rcl_node_options_copy, librcl), rcl_ret_t, (Ptr{rcl_node_options_t}, Ptr{rcl_node_options_t}), options, options_out)
end

function rcl_node_is_valid(node)
    ccall((:rcl_node_is_valid, librcl), Bool, (Ptr{rcl_node_t},), node)
end

function rcl_node_is_valid_except_context(node)
    ccall((:rcl_node_is_valid_except_context, librcl), Bool, (Ptr{rcl_node_t},), node)
end

function rcl_node_get_name(node)
    ccall((:rcl_node_get_name, librcl), Cstring, (Ptr{rcl_node_t},), node)
end

function rcl_node_get_namespace(node)
    ccall((:rcl_node_get_namespace, librcl), Cstring, (Ptr{rcl_node_t},), node)
end

function rcl_node_get_options(node)
    ccall((:rcl_node_get_options, librcl), Ptr{rcl_node_options_t}, (Ptr{rcl_node_t},), node)
end

function rcl_node_get_domain_id(node, domain_id)
    ccall((:rcl_node_get_domain_id, librcl), rcl_ret_t, (Ptr{rcl_node_t}, Ptr{Csize_t}), node, domain_id)
end

function rcl_node_get_rmw_handle(node)
    ccall((:rcl_node_get_rmw_handle, librcl), Ptr{rmw_node_t}, (Ptr{rcl_node_t},), node)
end

function rcl_node_get_rcl_instance_id(node)
    ccall((:rcl_node_get_rcl_instance_id, librcl), UInt64, (Ptr{rcl_node_t},), node)
end

function rcl_node_get_graph_guard_condition(node)
    ccall((:rcl_node_get_graph_guard_condition, librcl), Ptr{rcl_guard_condition_t}, (Ptr{rcl_node_t},), node)
end

function rcl_node_get_logger_name(node)
    ccall((:rcl_node_get_logger_name, librcl), Cstring, (Ptr{rcl_node_t},), node)
end
# Julia wrapper for header: /opt/ros/crystal/include/rcl/publisher.h
# Automatically generated using Clang.jl wrap_c

function rcl_get_zero_initialized_publisher()
    ccall((:rcl_get_zero_initialized_publisher, librcl), rcl_publisher_t, ())
end

function rcl_publisher_init(publisher, node, type_support, topic_name, options)
    ccall((:rcl_publisher_init, librcl), rcl_ret_t, (Ptr{rcl_publisher_t}, Ptr{rcl_node_t}, Ptr{rosidl_message_type_support_t}, Cstring, Ptr{rcl_publisher_options_t}), publisher, node, type_support, topic_name, options)
end

function rcl_publisher_fini(publisher, node)
    ccall((:rcl_publisher_fini, librcl), rcl_ret_t, (Ptr{rcl_publisher_t}, Ptr{rcl_node_t}), publisher, node)
end

function rcl_publisher_get_default_options()
    ccall((:rcl_publisher_get_default_options, librcl), rcl_publisher_options_t, ())
end

function rcl_publish(publisher, ros_message)
    ccall((:rcl_publish, librcl), rcl_ret_t, (Ptr{rcl_publisher_t}, Ptr{Cvoid}), publisher, ros_message)
end

function rcl_publish_serialized_message(publisher, serialized_message)
    ccall((:rcl_publish_serialized_message, librcl), rcl_ret_t, (Ptr{rcl_publisher_t}, Ptr{rcl_serialized_message_t}), publisher, serialized_message)
end

function rcl_publisher_get_topic_name(publisher)
    ccall((:rcl_publisher_get_topic_name, librcl), Cstring, (Ptr{rcl_publisher_t},), publisher)
end

function rcl_publisher_get_options(publisher)
    ccall((:rcl_publisher_get_options, librcl), Ptr{rcl_publisher_options_t}, (Ptr{rcl_publisher_t},), publisher)
end

function rcl_publisher_get_rmw_handle(publisher)
    ccall((:rcl_publisher_get_rmw_handle, librcl), Ptr{rmw_publisher_t}, (Ptr{rcl_publisher_t},), publisher)
end

function rcl_publisher_get_context(publisher)
    ccall((:rcl_publisher_get_context, librcl), Ptr{rcl_context_t}, (Ptr{rcl_publisher_t},), publisher)
end

function rcl_publisher_is_valid(publisher)
    ccall((:rcl_publisher_is_valid, librcl), Bool, (Ptr{rcl_publisher_t},), publisher)
end

function rcl_publisher_is_valid_except_context(publisher)
    ccall((:rcl_publisher_is_valid_except_context, librcl), Bool, (Ptr{rcl_publisher_t},), publisher)
end

function rcl_publisher_get_subscription_count(publisher, subscription_count)
    ccall((:rcl_publisher_get_subscription_count, librcl), rmw_ret_t, (Ptr{rcl_publisher_t}, Ptr{Csize_t}), publisher, subscription_count)
end
# Julia wrapper for header: /opt/ros/crystal/include/rcl/rcl.h
# Automatically generated using Clang.jl wrap_c

# Julia wrapper for header: /opt/ros/crystal/include/rcl/remap.h
# Automatically generated using Clang.jl wrap_c

function rcl_remap_topic_name(local_arguments, global_arguments, topic_name, node_name, node_namespace, allocator, output_name)
    ccall((:rcl_remap_topic_name, librcl), rcl_ret_t, (Ptr{rcl_arguments_t}, Ptr{rcl_arguments_t}, Cstring, Cstring, Cstring, rcl_allocator_t, Ptr{Cstring}), local_arguments, global_arguments, topic_name, node_name, node_namespace, allocator, output_name)
end

function rcl_remap_service_name(local_arguments, global_arguments, service_name, node_name, node_namespace, allocator, output_name)
    ccall((:rcl_remap_service_name, librcl), rcl_ret_t, (Ptr{rcl_arguments_t}, Ptr{rcl_arguments_t}, Cstring, Cstring, Cstring, rcl_allocator_t, Ptr{Cstring}), local_arguments, global_arguments, service_name, node_name, node_namespace, allocator, output_name)
end

function rcl_remap_node_name(local_arguments, global_arguments, node_name, allocator, output_name)
    ccall((:rcl_remap_node_name, librcl), rcl_ret_t, (Ptr{rcl_arguments_t}, Ptr{rcl_arguments_t}, Cstring, rcl_allocator_t, Ptr{Cstring}), local_arguments, global_arguments, node_name, allocator, output_name)
end

function rcl_remap_node_namespace(local_arguments, global_arguments, node_name, allocator, output_namespace)
    ccall((:rcl_remap_node_namespace, librcl), rcl_ret_t, (Ptr{rcl_arguments_t}, Ptr{rcl_arguments_t}, Cstring, rcl_allocator_t, Ptr{Cstring}), local_arguments, global_arguments, node_name, allocator, output_namespace)
end
# Julia wrapper for header: /opt/ros/crystal/include/rcl/service.h
# Automatically generated using Clang.jl wrap_c

function rcl_get_zero_initialized_service()
    ccall((:rcl_get_zero_initialized_service, librcl), rcl_service_t, ())
end

function rcl_service_init(service, node, type_support, service_name, options)
    ccall((:rcl_service_init, librcl), rcl_ret_t, (Ptr{rcl_service_t}, Ptr{rcl_node_t}, Ptr{rosidl_service_type_support_t}, Cstring, Ptr{rcl_service_options_t}), service, node, type_support, service_name, options)
end

function rcl_service_fini(service, node)
    ccall((:rcl_service_fini, librcl), rcl_ret_t, (Ptr{rcl_service_t}, Ptr{rcl_node_t}), service, node)
end

function rcl_service_get_default_options()
    ccall((:rcl_service_get_default_options, librcl), rcl_service_options_t, ())
end

function rcl_take_request(service, request_header, ros_request)
    ccall((:rcl_take_request, librcl), rcl_ret_t, (Ptr{rcl_service_t}, Ptr{rmw_request_id_t}, Ptr{Cvoid}), service, request_header, ros_request)
end

function rcl_send_response(service, response_header, ros_response)
    ccall((:rcl_send_response, librcl), rcl_ret_t, (Ptr{rcl_service_t}, Ptr{rmw_request_id_t}, Ptr{Cvoid}), service, response_header, ros_response)
end

function rcl_service_get_service_name(service)
    ccall((:rcl_service_get_service_name, librcl), Cstring, (Ptr{rcl_service_t},), service)
end

function rcl_service_get_options(service)
    ccall((:rcl_service_get_options, librcl), Ptr{rcl_service_options_t}, (Ptr{rcl_service_t},), service)
end

function rcl_service_get_rmw_handle(service)
    ccall((:rcl_service_get_rmw_handle, librcl), Ptr{rmw_service_t}, (Ptr{rcl_service_t},), service)
end

function rcl_service_is_valid(service)
    ccall((:rcl_service_is_valid, librcl), Bool, (Ptr{rcl_service_t},), service)
end
# Julia wrapper for header: /opt/ros/crystal/include/rcl/subscription.h
# Automatically generated using Clang.jl wrap_c

function rcl_get_zero_initialized_subscription()
    ccall((:rcl_get_zero_initialized_subscription, librcl), rcl_subscription_t, ())
end

function rcl_subscription_init(subscription, node, type_support, topic_name, options)
    ccall((:rcl_subscription_init, librcl), rcl_ret_t, (Ptr{rcl_subscription_t}, Ptr{rcl_node_t}, Ptr{rosidl_message_type_support_t}, Cstring, Ptr{rcl_subscription_options_t}), subscription, node, type_support, topic_name, options)
end

function rcl_subscription_fini(subscription, node)
    ccall((:rcl_subscription_fini, librcl), rcl_ret_t, (Ptr{rcl_subscription_t}, Ptr{rcl_node_t}), subscription, node)
end

function rcl_subscription_get_default_options()
    ccall((:rcl_subscription_get_default_options, librcl), rcl_subscription_options_t, ())
end

function rcl_take(subscription, ros_message, message_info)
    ccall((:rcl_take, librcl), rcl_ret_t, (Ptr{rcl_subscription_t}, Ptr{Cvoid}, Ptr{rmw_message_info_t}), subscription, ros_message, message_info)
end

function rcl_take_serialized_message(subscription, serialized_message, message_info)
    ccall((:rcl_take_serialized_message, librcl), rcl_ret_t, (Ptr{rcl_subscription_t}, Ptr{rcl_serialized_message_t}, Ptr{rmw_message_info_t}), subscription, serialized_message, message_info)
end

function rcl_subscription_get_topic_name(subscription)
    ccall((:rcl_subscription_get_topic_name, librcl), Cstring, (Ptr{rcl_subscription_t},), subscription)
end

function rcl_subscription_get_options(subscription)
    ccall((:rcl_subscription_get_options, librcl), Ptr{rcl_subscription_options_t}, (Ptr{rcl_subscription_t},), subscription)
end

function rcl_subscription_get_rmw_handle(subscription)
    ccall((:rcl_subscription_get_rmw_handle, librcl), Ptr{rmw_subscription_t}, (Ptr{rcl_subscription_t},), subscription)
end

function rcl_subscription_is_valid(subscription)
    ccall((:rcl_subscription_is_valid, librcl), Bool, (Ptr{rcl_subscription_t},), subscription)
end

function rcl_subscription_get_publisher_count(subscription, publisher_count)
    ccall((:rcl_subscription_get_publisher_count, librcl), rmw_ret_t, (Ptr{rcl_subscription_t}, Ptr{Csize_t}), subscription, publisher_count)
end
# Julia wrapper for header: /opt/ros/crystal/include/rcl/time.h
# Automatically generated using Clang.jl wrap_c

function rcl_clock_valid(clock)
    ccall((:rcl_clock_valid, librcl), Bool, (Ptr{rcl_clock_t},), clock)
end

function rcl_clock_init(clock_type, clock, allocator)
    ccall((:rcl_clock_init, librcl), rcl_ret_t, (rcl_clock_type_t, Ptr{rcl_clock_t}, Ptr{rcl_allocator_t}), clock_type, clock, allocator)
end

function rcl_clock_fini(clock)
    ccall((:rcl_clock_fini, librcl), rcl_ret_t, (Ptr{rcl_clock_t},), clock)
end

function rcl_ros_clock_init(clock, allocator)
    ccall((:rcl_ros_clock_init, librcl), rcl_ret_t, (Ptr{rcl_clock_t}, Ptr{rcl_allocator_t}), clock, allocator)
end

function rcl_ros_clock_fini(clock)
    ccall((:rcl_ros_clock_fini, librcl), rcl_ret_t, (Ptr{rcl_clock_t},), clock)
end

function rcl_steady_clock_init(clock, allocator)
    ccall((:rcl_steady_clock_init, librcl), rcl_ret_t, (Ptr{rcl_clock_t}, Ptr{rcl_allocator_t}), clock, allocator)
end

function rcl_steady_clock_fini(clock)
    ccall((:rcl_steady_clock_fini, librcl), rcl_ret_t, (Ptr{rcl_clock_t},), clock)
end

function rcl_system_clock_init(clock, allocator)
    ccall((:rcl_system_clock_init, librcl), rcl_ret_t, (Ptr{rcl_clock_t}, Ptr{rcl_allocator_t}), clock, allocator)
end

function rcl_system_clock_fini(clock)
    ccall((:rcl_system_clock_fini, librcl), rcl_ret_t, (Ptr{rcl_clock_t},), clock)
end

function rcl_difference_times(start, finish, delta)
    ccall((:rcl_difference_times, librcl), rcl_ret_t, (Ptr{rcl_time_point_t}, Ptr{rcl_time_point_t}, Ptr{rcl_duration_t}), start, finish, delta)
end

function rcl_clock_get_now(clock, time_point_value)
    ccall((:rcl_clock_get_now, librcl), rcl_ret_t, (Ptr{rcl_clock_t}, Ptr{rcl_time_point_value_t}), clock, time_point_value)
end

function rcl_enable_ros_time_override(clock)
    ccall((:rcl_enable_ros_time_override, librcl), rcl_ret_t, (Ptr{rcl_clock_t},), clock)
end

function rcl_disable_ros_time_override(clock)
    ccall((:rcl_disable_ros_time_override, librcl), rcl_ret_t, (Ptr{rcl_clock_t},), clock)
end

function rcl_is_enabled_ros_time_override(clock, is_enabled)
    ccall((:rcl_is_enabled_ros_time_override, librcl), rcl_ret_t, (Ptr{rcl_clock_t}, Ptr{Bool}), clock, is_enabled)
end

function rcl_set_ros_time_override(clock, time_value)
    ccall((:rcl_set_ros_time_override, librcl), rcl_ret_t, (Ptr{rcl_clock_t}, rcl_time_point_value_t), clock, time_value)
end

function rcl_clock_add_jump_callback(clock, threshold, callback, user_data)
    ccall((:rcl_clock_add_jump_callback, librcl), rcl_ret_t, (Ptr{rcl_clock_t}, rcl_jump_threshold_t, rcl_jump_callback_t, Ptr{Cvoid}), clock, threshold, callback, user_data)
end

function rcl_clock_remove_jump_callback(clock, callback, user_data)
    ccall((:rcl_clock_remove_jump_callback, librcl), rcl_ret_t, (Ptr{rcl_clock_t}, rcl_jump_callback_t, Ptr{Cvoid}), clock, callback, user_data)
end
# Julia wrapper for header: /opt/ros/crystal/include/rcl/timer.h
# Automatically generated using Clang.jl wrap_c

function rcl_get_zero_initialized_timer()
    ccall((:rcl_get_zero_initialized_timer, librcl), rcl_timer_t, ())
end

function rcl_timer_init(timer, clock, context, period, callback, allocator)
    ccall((:rcl_timer_init, librcl), rcl_ret_t, (Ptr{rcl_timer_t}, Ptr{rcl_clock_t}, Ptr{rcl_context_t}, Int64, rcl_timer_callback_t, rcl_allocator_t), timer, clock, context, period, callback, allocator)
end

function rcl_timer_fini(timer)
    ccall((:rcl_timer_fini, librcl), rcl_ret_t, (Ptr{rcl_timer_t},), timer)
end

function rcl_timer_call(timer)
    ccall((:rcl_timer_call, librcl), rcl_ret_t, (Ptr{rcl_timer_t},), timer)
end

function rcl_timer_clock(timer, clock)
    ccall((:rcl_timer_clock, librcl), rcl_ret_t, (Ptr{rcl_timer_t}, Ptr{Ptr{rcl_clock_t}}), timer, clock)
end

function rcl_timer_is_ready(timer, is_ready)
    ccall((:rcl_timer_is_ready, librcl), rcl_ret_t, (Ptr{rcl_timer_t}, Ptr{Bool}), timer, is_ready)
end

function rcl_timer_get_time_until_next_call(timer, time_until_next_call)
    ccall((:rcl_timer_get_time_until_next_call, librcl), rcl_ret_t, (Ptr{rcl_timer_t}, Ptr{Int64}), timer, time_until_next_call)
end

function rcl_timer_get_time_since_last_call(timer, time_since_last_call)
    ccall((:rcl_timer_get_time_since_last_call, librcl), rcl_ret_t, (Ptr{rcl_timer_t}, Ptr{Int64}), timer, time_since_last_call)
end

function rcl_timer_get_period(timer, period)
    ccall((:rcl_timer_get_period, librcl), rcl_ret_t, (Ptr{rcl_timer_t}, Ptr{Int64}), timer, period)
end

function rcl_timer_exchange_period(timer, new_period, old_period)
    ccall((:rcl_timer_exchange_period, librcl), rcl_ret_t, (Ptr{rcl_timer_t}, Int64, Ptr{Int64}), timer, new_period, old_period)
end

function rcl_timer_get_callback(timer)
    ccall((:rcl_timer_get_callback, librcl), rcl_timer_callback_t, (Ptr{rcl_timer_t},), timer)
end

function rcl_timer_exchange_callback(timer, new_callback)
    ccall((:rcl_timer_exchange_callback, librcl), rcl_timer_callback_t, (Ptr{rcl_timer_t}, rcl_timer_callback_t), timer, new_callback)
end

function rcl_timer_cancel(timer)
    ccall((:rcl_timer_cancel, librcl), rcl_ret_t, (Ptr{rcl_timer_t},), timer)
end

function rcl_timer_is_canceled(timer, is_canceled)
    ccall((:rcl_timer_is_canceled, librcl), rcl_ret_t, (Ptr{rcl_timer_t}, Ptr{Bool}), timer, is_canceled)
end

function rcl_timer_reset(timer)
    ccall((:rcl_timer_reset, librcl), rcl_ret_t, (Ptr{rcl_timer_t},), timer)
end

function rcl_timer_get_allocator(timer)
    ccall((:rcl_timer_get_allocator, librcl), Ptr{rcl_allocator_t}, (Ptr{rcl_timer_t},), timer)
end

function rcl_timer_get_guard_condition(timer)
    ccall((:rcl_timer_get_guard_condition, librcl), Ptr{rcl_guard_condition_t}, (Ptr{rcl_timer_t},), timer)
end
# Julia wrapper for header: /opt/ros/crystal/include/rcl/types.h
# Automatically generated using Clang.jl wrap_c

# Julia wrapper for header: /opt/ros/crystal/include/rcl/validate_topic_name.h
# Automatically generated using Clang.jl wrap_c

function rcl_validate_topic_name(topic_name, validation_result, invalid_index)
    ccall((:rcl_validate_topic_name, librcl), rcl_ret_t, (Cstring, Ptr{Cint}, Ptr{Csize_t}), topic_name, validation_result, invalid_index)
end

function rcl_validate_topic_name_with_size(topic_name, topic_name_length, validation_result, invalid_index)
    ccall((:rcl_validate_topic_name_with_size, librcl), rcl_ret_t, (Cstring, Csize_t, Ptr{Cint}, Ptr{Csize_t}), topic_name, topic_name_length, validation_result, invalid_index)
end

function rcl_topic_name_validation_result_string(validation_result)
    ccall((:rcl_topic_name_validation_result_string, librcl), Cstring, (Cint,), validation_result)
end
# Julia wrapper for header: /opt/ros/crystal/include/rcl/visibility_control.h
# Automatically generated using Clang.jl wrap_c

# Julia wrapper for header: /opt/ros/crystal/include/rcl/wait.h
# Automatically generated using Clang.jl wrap_c

function rcl_get_zero_initialized_wait_set()
    ccall((:rcl_get_zero_initialized_wait_set, librcl), rcl_wait_set_t, ())
end

function rcl_wait_set_init(wait_set, number_of_subscriptions, number_of_guard_conditions, number_of_timers, number_of_clients, number_of_services, allocator)
    ccall((:rcl_wait_set_init, librcl), rcl_ret_t, (Ptr{rcl_wait_set_t}, Csize_t, Csize_t, Csize_t, Csize_t, Csize_t, rcl_allocator_t), wait_set, number_of_subscriptions, number_of_guard_conditions, number_of_timers, number_of_clients, number_of_services, allocator)
end

function rcl_wait_set_fini(wait_set)
    ccall((:rcl_wait_set_fini, librcl), rcl_ret_t, (Ptr{rcl_wait_set_t},), wait_set)
end

function rcl_wait_set_get_allocator(wait_set, allocator)
    ccall((:rcl_wait_set_get_allocator, librcl), rcl_ret_t, (Ptr{rcl_wait_set_t}, Ptr{rcl_allocator_t}), wait_set, allocator)
end

function rcl_wait_set_add_subscription(wait_set, subscription, index)
    ccall((:rcl_wait_set_add_subscription, librcl), rcl_ret_t, (Ptr{rcl_wait_set_t}, Ptr{rcl_subscription_t}, Ptr{Csize_t}), wait_set, subscription, index)
end

function rcl_wait_set_clear(wait_set)
    ccall((:rcl_wait_set_clear, librcl), rcl_ret_t, (Ptr{rcl_wait_set_t},), wait_set)
end

function rcl_wait_set_resize(wait_set, subscriptions_size, guard_conditions_size, timers_size, clients_size, services_size)
    ccall((:rcl_wait_set_resize, librcl), rcl_ret_t, (Ptr{rcl_wait_set_t}, Csize_t, Csize_t, Csize_t, Csize_t, Csize_t), wait_set, subscriptions_size, guard_conditions_size, timers_size, clients_size, services_size)
end

function rcl_wait_set_add_guard_condition(wait_set, guard_condition, index)
    ccall((:rcl_wait_set_add_guard_condition, librcl), rcl_ret_t, (Ptr{rcl_wait_set_t}, Ptr{rcl_guard_condition_t}, Ptr{Csize_t}), wait_set, guard_condition, index)
end

function rcl_wait_set_add_timer(wait_set, timer, index)
    ccall((:rcl_wait_set_add_timer, librcl), rcl_ret_t, (Ptr{rcl_wait_set_t}, Ptr{rcl_timer_t}, Ptr{Csize_t}), wait_set, timer, index)
end

function rcl_wait_set_add_client(wait_set, client, index)
    ccall((:rcl_wait_set_add_client, librcl), rcl_ret_t, (Ptr{rcl_wait_set_t}, Ptr{rcl_client_t}, Ptr{Csize_t}), wait_set, client, index)
end

function rcl_wait_set_add_service(wait_set, service, index)
    ccall((:rcl_wait_set_add_service, librcl), rcl_ret_t, (Ptr{rcl_wait_set_t}, Ptr{rcl_service_t}, Ptr{Csize_t}), wait_set, service, index)
end

function rcl_wait(wait_set, timeout)
    ccall((:rcl_wait, librcl), rcl_ret_t, (Ptr{rcl_wait_set_t}, Int64), wait_set, timeout)
end


text:
module OnlineStatsBase

import LearnBase: nobs, value, fit!

export
    OnlineStat, Weight,
    nobs, value, fit!, _fit!, eachrow, eachcol,
    EqualWeight, ExponentialWeight, LearningRate, LearningRate2, HarmonicWeight, McclainWeight,
    OnlineIterator

abstract type OnlineStat{T} end

nobs(o::OnlineStat) = o.n

"""
    value(o::OnlineStat)

Calculate the value of the stat from its "sufficient statistics".
"""
@generated function value(o::OnlineStat)
    r = first(fieldnames(o))
    return :(o.$r)
end

#-----------------------------------------------------------------------# Base
Base.:(==)(o::OnlineStat, o2::OnlineStat) = false
function Base.:(==)(o1::T, o2::T) where {T<:OnlineStat}
    nms = fieldnames(typeof(o1))
    all(getfield.(Ref(o1), nms) .== getfield.(Ref(o2), nms))
end

Base.copy(o::OnlineStat) = deepcopy(o)
function Base.merge!(o::OnlineStat, o2::OnlineStat)
    (nobs(o) > 0 || nobs(o2) > 0) && _merge!(o, o2)
    o
end
_merge!(o, o2) = @warn("Merging $(name(o2)) into $(name(o)) is not well-defined.  No merging occurred.")
Base.merge(o::OnlineStat, o2::OnlineStat) = merge!(copy(o), o2)

#-----------------------------------------------------------------------# Show
function Base.show(io::IO, o::OnlineStat)
    print(io, name(o, false, false), ": ")
    print(io, "n=", nobs(o))
    print(io, " | value=")
    show(IOContext(io, :compact => true), value(o))
end
function name(o, withmodule = false, withparams = true)
    s = string(typeof(o))
    if !withmodule
        s = replace(s, r"([a-zA-Z]*\.)" => "")  # remove text that ends in period
    end
    if !withparams
        s = replace(s, r"\{(.*)" => "")  # remove "{" to the end of the string
    end
    s
end

#-----------------------------------------------------------------------# fit!
"""
    fit!(stat::OnlineStat, data)

Update the "sufficient statistics" of a `stat` with more data.   If `typeof(data)` is not
the type of a single observation for the provided `stat`, `fit!` will attempt to iterate
through and `fit!` each item in `data`.  Therefore, `fit!(Mean(), 1:10)` translates
roughly to:

```
o = Mean()
for x in 1:10
    fit!(o, x)
end
```
"""
fit!(o::OnlineStat{T}, yi::T) where {T} = (_fit!(o, yi); return o)

function fit!(o::OnlineStat{I}, y::T) where {I, T}
    T == eltype(y) && error("The input for $(name(o,false,false)) is a $I.  Found $T.")
    for yi in y
        fit!(o, yi)
    end
    o
end

# fit!(o::OnlineStat, y::Nothing) = nothing

#-----------------------------------------------------------------------# utils
function _fit!(o::OnlineStat{T}, arg) where {T}
    error("A $(typeof(arg)) is not a single observation for $((name(o, false, true)))")
end

"""
    smooth(a, b, γ)

Weighted average of `a` and `b` with weight `γ`.
"""
smooth(a, b, γ) = a + γ * (b - a)

"""
    smooth!(a, b, γ)

Update `a` in place by applying the [`smooth`](@ref) function elementwise with `b`.
"""
function smooth!(a, b, γ)
    for (i, bi) in zip(eachindex(a), b)
        a[i] = smooth(a[i], bi, γ)
    end
end

"""
    smooth_syr!(A::AbstractMatrix, x, γ::Number)

Weighted average of symmetric rank-1 update.  Updates the upper triangle of:

`A = (1 - γ) * A + γ * x * x'`
"""
function smooth_syr!(A::AbstractMatrix, x, γ::Number)
    for j in 1:size(A, 2), i in 1:j
        A[i, j] = smooth(A[i,j], x[i] * conj(x[j]), γ)
    end
end

change_denom(num, from = nobs(o), to = nobs(o) - 1) = (from / to) * num

#-----------------------------------------------------------------------# OnlineIterator
struct OnlineIterator{R,T,S}
    thing::T
    buffer::S
    OnlineIterator{R}(thing::T, buffer::S) where {T,S,R} = new{R,T,S}(thing, buffer)
end

Base.iterate(o::OnlineIterator, i=1) = i > length(o) ? nothing : (o[i], i+1)
Base.keys(o::OnlineIterator) = Base.OneTo(length(o))

"""
    eachrow(x::AbstractMatrix)
    eachrow(x::AbstractMatrix, y::AbstractVector)

Iterator over the rows of `x` (paired in a tuple with the values of `y`).

# Example
    for xi in eachrow(rand(3,2))
        println(xi)
    end
"""
eachrow(args...) = eachrow(args)

"""
    eachcol(x::AbstractMatrix)
    eachcol(x::AbstractMatrix, y::AbstractVector)

Iterator over the columns of `x` (paired in a tuple with the values of `y`).

# Example
    for xi in eachcol(rand(3,2))
        println(xi)
    end
"""
eachcol(args...) = eachcol(args)

# helpers
function copyrow!(buffer::Vector, x::AbstractMatrix, i::Int)
    for j in eachindex(buffer)
        buffer[j] = x[i, j]
    end
    buffer
end
function copycol!(buffer::Vector, x::AbstractMatrix, j::Int)
    for i in eachindex(buffer)
        buffer[i] = x[i, j]
    end
    buffer
end

# Matrix rows
Base.length(o::OnlineIterator{:row, <:AbstractMatrix}) = size(o.thing, 1)
Base.getindex(o::OnlineIterator{:row, <:AbstractMatrix}, i::Int) = copyrow!(o.buffer, o.thing, i)
eachrow(m::AbstractMatrix{T}) where {T} = OnlineIterator{:row}(m, Vector{T}(undef, size(m, 2)))

# Matrix cols
Base.length(o::OnlineIterator{:col, <:AbstractMatrix}) = size(o.thing, 2)
Base.getindex(o::OnlineIterator{:col, <:AbstractMatrix}, i::Int) = copycol!(o.buffer, o.thing, i)
eachcol(m::AbstractMatrix{T}) where {T} = OnlineIterator{:col}(m, Vector{T}(undef, size(m, 1)))

const XY = Tuple{T, S} where {T<:AbstractMatrix, S<:AbstractVector}

# XY rows
Base.length(o::OnlineIterator{:row, <:XY}) = size(o.thing[1], 1)
Base.getindex(o::OnlineIterator{:row, <:XY}, i::Int) = (copyrow!(o.buffer, o.thing[1], i), o.thing[2][i])
eachrow(t::XY) = OnlineIterator{:row}(t, Vector{eltype(t[1])}(undef, size(t[1], 2)))

# XY cols
Base.length(o::OnlineIterator{:col, <:XY}) = size(o.thing[1], 2)
Base.getindex(o::OnlineIterator{:col, <:XY}, i::Int) = (copycol!(o.buffer, o.thing[1], i), o.thing[2][i])
eachcol(t::XY) = OnlineIterator{:col}(t, Vector{eltype(t[1])}(undef, size(t[1], 1)))

include("weight.jl")
end


text:
############################################################################################
# Higher Order Data Memory
HO_latent = randn(_rng, N)
HO_data = randn(_rng, size(HO_latent, 1))
HO_param = (
    μ₀=Param(0.0, Normal(0.0, 10.0)),
    μ=Param(-0.50, Normal(0.0, 10.0)),
    σ=Param(0.250, Gamma(0.5, 1.0)),
    ϕ=Param(0.90, Uniform(-1.0, 1.0)),
    latent=Param(HO_latent, [Normal(0.0, 10) for i in Base.OneTo(N)]),
)
struct Markov_HO <: ModelName end
markov_HO = ModelWrapper(Markov_HO(), HO_param)
markov_HO_objective = Objective(markov_HO, HO_data, :latent)

function BaytesFilters.dynamics(objective::Objective{<:ModelWrapper{<:Markov_HO}})
    @unpack model, data = objective
    @unpack μ₀, μ, σ, ϕ = model.val

    initialˢ = Normal(μ, σ / sqrt((1 - ϕ^2)))
    function transition(particles, iter)
        return Normal(μ + ϕ * (mean(view(particles, (iter - 2):(iter - 1))) - μ), σ)
    end
    function evidence(particles, iter)
        return Normal(μ₀, sqrt(exp(mean(view(particles, (iter - 3):iter)))))
    end
    return Markov(initialˢ, transition, evidence)
end
############################################################################################


text:
######################################################################
# visualization.jl
#
# extends D3Trees to make a graph
#
# TODO:
#  Don't enumerate over belief nodes; start from root and move down.
######################################################################

using D3Trees

function D3Trees.D3Tree(planner::AEMSPlanner; title="AEMS Tree", kwargs...)
    G = planner.G
    children = Array{Vector{Int}}(0)
    text = String[]

    # create vector of vectors
    for (bni,bn) in enumerate(G.belief_nodes)
        if !isfringe(bn)
            push!(children, collect(bn.children) + G.nb)
        else
            push!(children, [])
        end
        if bni == 1
            push!(text, "b0\nU=$(round(bn.U,2))\nL=$(round(bn.L,2))")
        else
            p = "P(o$(bn.oi)) = $(round(bn.po,2))"
            L = "\nL=$(round(bn.L,2))"
            U = "\nU=$(round(bn.U,2))"
            push!(text, string(p,U,L))
        end
    end

    for an in G.action_nodes
        push!(children, collect(an.children))
        r = "r(a$(an.ai)) = $(round(an.r))"
        L = "\nL=$(round(an.L,2))"
        U = "\nU=$(round(an.U,2))"
        push!(text, string(r,U,L))
    end

    D3Tree(children, text=text, title=title, kwargs...)
end


text:
# Constants for GeMM

const boltz = 1.38064852e-23 # J/K = m2⋅kg/(s2⋅K)
const act = 1e-19 # activation energy /J, ca. 0.63eV - Brown et al. 2004

max_stars_repo_name: CCTB-Ecomods/gemm

max_issues_repo_name: CCTB-Ecomods/gemm

max_forks_repo_name: CCTB-Ecomods/gemm