#=
This file is auto-generated. Do not edit.
=#

#! format: off

"""
    mutable struct AggregateDistributedGenerationA <: DynamicInjection
        name::String
        Pf_Flag::Int
        Freq_Flag::Int
        PQ_Flag::Int
        Gen_Flag::Int
        Vtrip_Flag::Int
        Ftrip_Flag::Int
        T_rv::Float64
        Trf::Float64
        dbd_pnts::Tuple{Float64, Float64}
        K_qv::Float64
        Tp::Float64
        T_iq::Float64
        D_dn::Float64
        D_up::Float64
        fdbd_pnts::Tuple{Float64, Float64}
        fe_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
        P_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
        dP_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
        Tpord::Float64
        Kpg::Float64
        Kig::Float64
        I_max::Float64
        vl_pnts::Vector{Tuple{Float64,Float64}}
        vh_pnts::Vector{Tuple{Float64,Float64}}
        Vrfrac::Float64
        fl::Float64
        fh::Float64
        tfl::Float64
        tfh::Float64
        Tg::Float64
        rrpwr::Float64
        Tv::Float64
        Vpr::Float64
        Iq_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
        V_ref::Float64
        Pfa_ref::Float64
        Q_ref::Float64
        P_ref::Float64
        base_power::Float64
        states::Vector{Symbol}
        n_states::Int
        ext::Dict{String, Any}
        internal::InfrastructureSystemsInternal
    end

Parameters of the DERA1 model in PSS/E

# Arguments
- `name::String`
- `Pf_Flag::Int`: Flag for Power Factor Control, validation range: `(0, 1)`
- `Freq_Flag::Int`: Flag to enable/disable frequency control, validation range: `(0, 1)`
- `PQ_Flag::Int`: Flag used to enforce maximum current, validation range: `(0, 1)`
- `Gen_Flag::Int`: Flag to specify generator or storage, validation range: `(0, 1)`
- `Vtrip_Flag::Int`: Flag to enable/disable voltage trip logic, validation range: `(0, 1)`
- `Ftrip_Flag::Int`: Flag to enable/disable frequency trip logic, validation range: `(0, 1)`
- `T_rv::Float64`: Voltage measurement transducer time constant, validation range: `(0, nothing)`
- `Trf::Float64`: Frequency measurement transducer time constant, validation range: `(0, nothing)`
- `dbd_pnts::Tuple{Float64, Float64}`: Voltage deadband thresholds `(dbd1, dbd2)`
- `K_qv::Float64`: Proportional voltage control gain (pu), validation range: `(0, nothing)`
- `Tp::Float64`: Power measurement transducer time constant, validation range: `(0, nothing)`
- `T_iq::Float64`: Time constant for low-pass filter for state q_V when QFlag = 0, validation range: `(0, nothing)`
- `D_dn::Float64`: Reciprocal of droop for over-frequency conditions (>0) (pu), validation range: `(0, nothing)`
- `D_up::Float64`: Reciprocal of droop for under-frequency conditions <=0) (pu), validation range: `(0, nothing)`
- `fdbd_pnts::Tuple{Float64, Float64}`: Frequency control deadband thresholds `(fdbd1, fdbd2)`
- `fe_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}`: Frequency error limits (femin, femax)
- `P_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}`: Power limits (Pmin, Pmax)
- `dP_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}`: Power reference ramp rate limits (dPmin, dPmax)
- `Tpord::Float64`: Power filter time constant, validation range: `(0, nothing)`
- `Kpg::Float64`: PI controller proportional gain (pu), validation range: `(0, nothing)`
- `Kig::Float64`: PI controller integral gain (pu), validation range: `(0, nothing)`
- `I_max::Float64`: Maximum limit on total converter current (pu), validation range: `(0, nothing)`
- `vl_pnts::Vector{Tuple{Float64,Float64}}`: Low voltage cutout points `[(tv10, vl0), (tv11, vl1)]`
- `vh_pnts::Vector{Tuple{Float64,Float64}}`: High voltage cutout points `[(tvh0, vh0), (tvh1, vh1)]`
- `Vrfrac::Float64`: Fraction of device that recovers after voltage comes back to within vl1 < V < vh1 (0 <= Vrfrac <= 1), validation range: `(0, 1)`
- `fl::Float64`: Inverter frequency break-point for low frequency cut-out (Hz), validation range: `(0, nothing)`
- `fh::Float64`: Inverter frequency break-point for high frequency cut-out (Hz), validation range: `(0, nothing)`
- `tfl::Float64`: Low frequency cut-out timer corresponding to frequency fl (s), validation range: `(0, nothing)`
- `tfh::Float64`: High frequency cut-out timer corresponding to frequency fh (s), validation range: `(0, nothing)`
- `Tg::Float64`: Current control time constant (to represent behavior of inner control loops) (> 0) (s), validation range: `(0, nothing)`
- `rrpwr::Float64`: Ramp rate for real power increase following a fault (pu/s), validation range: `(0, nothing)`
- `Tv::Float64`: Time constant on the output of the multiplier (s), validation range: `(0, nothing)`
- `Vpr::Float64`: Voltage below which frequency tripping is disabled (pu), validation range: `(0, nothing)`
- `Iq_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}`: Reactive current injection limits (Iqll, Iqhl)
- `V_ref::Float64`: User defined voltage reference. If 0, PSID initializes to initial terminal voltage, validation range: `(0, nothing)`
- `Pfa_ref::Float64`: Reference power factor, validation range: `(0, nothing)`
- `Q_ref::Float64`: Reference reactive power, in pu, validation range: `(0, nothing)`
- `P_ref::Float64`: Reference active power, in pu, validation range: `(0, nothing)`
- `base_power::Float64`: Base power
- `states::Vector{Symbol}`: The states of AggregateDistributedGenerationA depends on the Flags
- `n_states::Int`: The states of AggregateDistributedGenerationA depends on the Flags
- `ext::Dict{String, Any}`
- `internal::InfrastructureSystemsInternal`: power system internal reference, do not modify
"""
mutable struct AggregateDistributedGenerationA <: DynamicInjection
    name::String
    "Flag for Power Factor Control"
    Pf_Flag::Int
    "Flag to enable/disable frequency control"
    Freq_Flag::Int
    "Flag used to enforce maximum current"
    PQ_Flag::Int
    "Flag to specify generator or storage"
    Gen_Flag::Int
    "Flag to enable/disable voltage trip logic"
    Vtrip_Flag::Int
    "Flag to enable/disable frequency trip logic"
    Ftrip_Flag::Int
    "Voltage measurement transducer time constant"
    T_rv::Float64
    "Frequency measurement transducer time constant"
    Trf::Float64
    "Voltage deadband thresholds `(dbd1, dbd2)`"
    dbd_pnts::Tuple{Float64, Float64}
    "Proportional voltage control gain (pu)"
    K_qv::Float64
    "Power measurement transducer time constant"
    Tp::Float64
    "Time constant for low-pass filter for state q_V when QFlag = 0"
    T_iq::Float64
    "Reciprocal of droop for over-frequency conditions (>0) (pu)"
    D_dn::Float64
    "Reciprocal of droop for under-frequency conditions <=0) (pu)"
    D_up::Float64
    "Frequency control deadband thresholds `(fdbd1, fdbd2)`"
    fdbd_pnts::Tuple{Float64, Float64}
    "Frequency error limits (femin, femax)"
    fe_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
    "Power limits (Pmin, Pmax)"
    P_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
    "Power reference ramp rate limits (dPmin, dPmax)"
    dP_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
    "Power filter time constant"
    Tpord::Float64
    "PI controller proportional gain (pu)"
    Kpg::Float64
    "PI controller integral gain (pu)"
    Kig::Float64
    "Maximum limit on total converter current (pu)"
    I_max::Float64
    "Low voltage cutout points `[(tv10, vl0), (tv11, vl1)]`"
    vl_pnts::Vector{Tuple{Float64,Float64}}
    "High voltage cutout points `[(tvh0, vh0), (tvh1, vh1)]`"
    vh_pnts::Vector{Tuple{Float64,Float64}}
    "Fraction of device that recovers after voltage comes back to within vl1 < V < vh1 (0 <= Vrfrac <= 1)"
    Vrfrac::Float64
    "Inverter frequency break-point for low frequency cut-out (Hz)"
    fl::Float64
    "Inverter frequency break-point for high frequency cut-out (Hz)"
    fh::Float64
    "Low frequency cut-out timer corresponding to frequency fl (s)"
    tfl::Float64
    "High frequency cut-out timer corresponding to frequency fh (s)"
    tfh::Float64
    "Current control time constant (to represent behavior of inner control loops) (> 0) (s)"
    Tg::Float64
    "Ramp rate for real power increase following a fault (pu/s)"
    rrpwr::Float64
    "Time constant on the output of the multiplier (s)"
    Tv::Float64
    "Voltage below which frequency tripping is disabled (pu)"
    Vpr::Float64
    "Reactive current injection limits (Iqll, Iqhl)"
    Iq_lim::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
    "User defined voltage reference. If 0, PSID initializes to initial terminal voltage"
    V_ref::Float64
    "Reference power factor"
    Pfa_ref::Float64
    "Reference reactive power, in pu"
    Q_ref::Float64
    "Reference active power, in pu"
    P_ref::Float64
    "Base power"
    base_power::Float64
    "The states of AggregateDistributedGenerationA depends on the Flags"
    states::Vector{Symbol}
    "The states of AggregateDistributedGenerationA depends on the Flags"
    n_states::Int
    ext::Dict{String, Any}
    "power system internal reference, do not modify"
    internal::InfrastructureSystemsInternal
end

function AggregateDistributedGenerationA(name, Pf_Flag, Freq_Flag, PQ_Flag, Gen_Flag, Vtrip_Flag, Ftrip_Flag, T_rv, Trf, dbd_pnts, K_qv, Tp, T_iq, D_dn, D_up, fdbd_pnts, fe_lim, P_lim, dP_lim, Tpord, Kpg, Kig, I_max, vl_pnts, vh_pnts, Vrfrac, fl, fh, tfl, tfh, Tg, rrpwr, Tv, Vpr, Iq_lim, V_ref=1.0, Pfa_ref=0.0, Q_ref=0.0, P_ref=1.0, base_power=100.0, ext=Dict{String, Any}(), )
    AggregateDistributedGenerationA(name, Pf_Flag, Freq_Flag, PQ_Flag, Gen_Flag, Vtrip_Flag, Ftrip_Flag, T_rv, Trf, dbd_pnts, K_qv, Tp, T_iq, D_dn, D_up, fdbd_pnts, fe_lim, P_lim, dP_lim, Tpord, Kpg, Kig, I_max, vl_pnts, vh_pnts, Vrfrac, fl, fh, tfl, tfh, Tg, rrpwr, Tv, Vpr, Iq_lim, V_ref, Pfa_ref, Q_ref, P_ref, base_power, ext, PowerSystems.get_AggregateDistributedGenerationA_states(Freq_Flag)[1], PowerSystems.get_AggregateDistributedGenerationA_states(Freq_Flag)[2], InfrastructureSystemsInternal(), )
end

function AggregateDistributedGenerationA(; name, Pf_Flag, Freq_Flag, PQ_Flag, Gen_Flag, Vtrip_Flag, Ftrip_Flag, T_rv, Trf, dbd_pnts, K_qv, Tp, T_iq, D_dn, D_up, fdbd_pnts, fe_lim, P_lim, dP_lim, Tpord, Kpg, Kig, I_max, vl_pnts, vh_pnts, Vrfrac, fl, fh, tfl, tfh, Tg, rrpwr, Tv, Vpr, Iq_lim, V_ref=1.0, Pfa_ref=0.0, Q_ref=0.0, P_ref=1.0, base_power=100.0, states=PowerSystems.get_AggregateDistributedGenerationA_states(Freq_Flag)[1], n_states=PowerSystems.get_AggregateDistributedGenerationA_states(Freq_Flag)[2], ext=Dict{String, Any}(), internal=InfrastructureSystemsInternal(), )
    AggregateDistributedGenerationA(name, Pf_Flag, Freq_Flag, PQ_Flag, Gen_Flag, Vtrip_Flag, Ftrip_Flag, T_rv, Trf, dbd_pnts, K_qv, Tp, T_iq, D_dn, D_up, fdbd_pnts, fe_lim, P_lim, dP_lim, Tpord, Kpg, Kig, I_max, vl_pnts, vh_pnts, Vrfrac, fl, fh, tfl, tfh, Tg, rrpwr, Tv, Vpr, Iq_lim, V_ref, Pfa_ref, Q_ref, P_ref, base_power, states, n_states, ext, internal, )
end

# Constructor for demo purposes; non-functional.
function AggregateDistributedGenerationA(::Nothing)
    AggregateDistributedGenerationA(;
        name="init",
        Pf_Flag=0,
        Freq_Flag=0,
        PQ_Flag=0,
        Gen_Flag=0,
        Vtrip_Flag=0,
        Ftrip_Flag=0,
        T_rv=0,
        Trf=0,
        dbd_pnts=(0.0, 0.0),
        K_qv=0,
        Tp=0,
        T_iq=0,
        D_dn=0,
        D_up=0,
        fdbd_pnts=(0.0, 0.0),
        fe_lim=(min=0.0, max=0.0),
        P_lim=(min=0.0, max=0.0),
        dP_lim=(min=0.0, max=0.0),
        Tpord=0,
        Kpg=0,
        Kig=0,
        I_max=0,
        vl_pnts=[(0.0, 0.0), (0.0, 0.0)],
        vh_pnts=[(0.0, 0.0), (0.0, 0.0)],
        Vrfrac=0,
        fl=0,
        fh=0,
        tfl=0,
        tfh=0,
        Tg=0,
        rrpwr=0,
        Tv=0,
        Vpr=0,
        Iq_lim=(min=0.0, max=0.0),
        V_ref=0,
        Pfa_ref=0,
        Q_ref=0,
        P_ref=0,
        base_power=0,
        ext=Dict{String, Any}(),
    )
end

"""Get [`AggregateDistributedGenerationA`](@ref) `name`."""
get_name(value::AggregateDistributedGenerationA) = value.name
"""Get [`AggregateDistributedGenerationA`](@ref) `Pf_Flag`."""
get_Pf_Flag(value::AggregateDistributedGenerationA) = value.Pf_Flag
"""Get [`AggregateDistributedGenerationA`](@ref) `Freq_Flag`."""
get_Freq_Flag(value::AggregateDistributedGenerationA) = value.Freq_Flag
"""Get [`AggregateDistributedGenerationA`](@ref) `PQ_Flag`."""
get_PQ_Flag(value::AggregateDistributedGenerationA) = value.PQ_Flag
"""Get [`AggregateDistributedGenerationA`](@ref) `Gen_Flag`."""
get_Gen_Flag(value::AggregateDistributedGenerationA) = value.Gen_Flag
"""Get [`AggregateDistributedGenerationA`](@ref) `Vtrip_Flag`."""
get_Vtrip_Flag(value::AggregateDistributedGenerationA) = value.Vtrip_Flag
"""Get [`AggregateDistributedGenerationA`](@ref) `Ftrip_Flag`."""
get_Ftrip_Flag(value::AggregateDistributedGenerationA) = value.Ftrip_Flag
"""Get [`AggregateDistributedGenerationA`](@ref) `T_rv`."""
get_T_rv(value::AggregateDistributedGenerationA) = value.T_rv
"""Get [`AggregateDistributedGenerationA`](@ref) `Trf`."""
get_Trf(value::AggregateDistributedGenerationA) = value.Trf
"""Get [`AggregateDistributedGenerationA`](@ref) `dbd_pnts`."""
get_dbd_pnts(value::AggregateDistributedGenerationA) = value.dbd_pnts
"""Get [`AggregateDistributedGenerationA`](@ref) `K_qv`."""
get_K_qv(value::AggregateDistributedGenerationA) = value.K_qv
"""Get [`AggregateDistributedGenerationA`](@ref) `Tp`."""
get_Tp(value::AggregateDistributedGenerationA) = value.Tp
"""Get [`AggregateDistributedGenerationA`](@ref) `T_iq`."""
get_T_iq(value::AggregateDistributedGenerationA) = value.T_iq
"""Get [`AggregateDistributedGenerationA`](@ref) `D_dn`."""
get_D_dn(value::AggregateDistributedGenerationA) = value.D_dn
"""Get [`AggregateDistributedGenerationA`](@ref) `D_up`."""
get_D_up(value::AggregateDistributedGenerationA) = value.D_up
"""Get [`AggregateDistributedGenerationA`](@ref) `fdbd_pnts`."""
get_fdbd_pnts(value::AggregateDistributedGenerationA) = value.fdbd_pnts
"""Get [`AggregateDistributedGenerationA`](@ref) `fe_lim`."""
get_fe_lim(value::AggregateDistributedGenerationA) = value.fe_lim
"""Get [`AggregateDistributedGenerationA`](@ref) `P_lim`."""
get_P_lim(value::AggregateDistributedGenerationA) = value.P_lim
"""Get [`AggregateDistributedGenerationA`](@ref) `dP_lim`."""
get_dP_lim(value::AggregateDistributedGenerationA) = value.dP_lim
"""Get [`AggregateDistributedGenerationA`](@ref) `Tpord`."""
get_Tpord(value::AggregateDistributedGenerationA) = value.Tpord
"""Get [`AggregateDistributedGenerationA`](@ref) `Kpg`."""
get_Kpg(value::AggregateDistributedGenerationA) = value.Kpg
"""Get [`AggregateDistributedGenerationA`](@ref) `Kig`."""
get_Kig(value::AggregateDistributedGenerationA) = value.Kig
"""Get [`AggregateDistributedGenerationA`](@ref) `I_max`."""
get_I_max(value::AggregateDistributedGenerationA) = value.I_max
"""Get [`AggregateDistributedGenerationA`](@ref) `vl_pnts`."""
get_vl_pnts(value::AggregateDistributedGenerationA) = value.vl_pnts
"""Get [`AggregateDistributedGenerationA`](@ref) `vh_pnts`."""
get_vh_pnts(value::AggregateDistributedGenerationA) = value.vh_pnts
"""Get [`AggregateDistributedGenerationA`](@ref) `Vrfrac`."""
get_Vrfrac(value::AggregateDistributedGenerationA) = value.Vrfrac
"""Get [`AggregateDistributedGenerationA`](@ref) `fl`."""
get_fl(value::AggregateDistributedGenerationA) = value.fl
"""Get [`AggregateDistributedGenerationA`](@ref) `fh`."""
get_fh(value::AggregateDistributedGenerationA) = value.fh
"""Get [`AggregateDistributedGenerationA`](@ref) `tfl`."""
get_tfl(value::AggregateDistributedGenerationA) = value.tfl
"""Get [`AggregateDistributedGenerationA`](@ref) `tfh`."""
get_tfh(value::AggregateDistributedGenerationA) = value.tfh
"""Get [`AggregateDistributedGenerationA`](@ref) `Tg`."""
get_Tg(value::AggregateDistributedGenerationA) = value.Tg
"""Get [`AggregateDistributedGenerationA`](@ref) `rrpwr`."""
get_rrpwr(value::AggregateDistributedGenerationA) = value.rrpwr
"""Get [`AggregateDistributedGenerationA`](@ref) `Tv`."""
get_Tv(value::AggregateDistributedGenerationA) = value.Tv
"""Get [`AggregateDistributedGenerationA`](@ref) `Vpr`."""
get_Vpr(value::AggregateDistributedGenerationA) = value.Vpr
"""Get [`AggregateDistributedGenerationA`](@ref) `Iq_lim`."""
get_Iq_lim(value::AggregateDistributedGenerationA) = value.Iq_lim
"""Get [`AggregateDistributedGenerationA`](@ref) `V_ref`."""
get_V_ref(value::AggregateDistributedGenerationA) = value.V_ref
"""Get [`AggregateDistributedGenerationA`](@ref) `Pfa_ref`."""
get_Pfa_ref(value::AggregateDistributedGenerationA) = value.Pfa_ref
"""Get [`AggregateDistributedGenerationA`](@ref) `Q_ref`."""
get_Q_ref(value::AggregateDistributedGenerationA) = value.Q_ref
"""Get [`AggregateDistributedGenerationA`](@ref) `P_ref`."""
get_P_ref(value::AggregateDistributedGenerationA) = value.P_ref
"""Get [`AggregateDistributedGenerationA`](@ref) `base_power`."""
get_base_power(value::AggregateDistributedGenerationA) = value.base_power
"""Get [`AggregateDistributedGenerationA`](@ref) `states`."""
get_states(value::AggregateDistributedGenerationA) = value.states
"""Get [`AggregateDistributedGenerationA`](@ref) `n_states`."""
get_n_states(value::AggregateDistributedGenerationA) = value.n_states
"""Get [`AggregateDistributedGenerationA`](@ref) `ext`."""
get_ext(value::AggregateDistributedGenerationA) = value.ext
"""Get [`AggregateDistributedGenerationA`](@ref) `internal`."""
get_internal(value::AggregateDistributedGenerationA) = value.internal

"""Set [`AggregateDistributedGenerationA`](@ref) `Pf_Flag`."""
set_Pf_Flag!(value::AggregateDistributedGenerationA, val) = value.Pf_Flag = val
"""Set [`AggregateDistributedGenerationA`](@ref) `Freq_Flag`."""
set_Freq_Flag!(value::AggregateDistributedGenerationA, val) = value.Freq_Flag = val
"""Set [`AggregateDistributedGenerationA`](@ref) `PQ_Flag`."""
set_PQ_Flag!(value::AggregateDistributedGenerationA, val) = value.PQ_Flag = val
"""Set [`AggregateDistributedGenerationA`](@ref) `Gen_Flag`."""
set_Gen_Flag!(value::AggregateDistributedGenerationA, val) = value.Gen_Flag = val
"""Set [`AggregateDistributedGenerationA`](@ref) `Vtrip_Flag`."""
set_Vtrip_Flag!(value::AggregateDistributedGenerationA, val) = value.Vtrip_Flag = val
"""Set [`AggregateDistributedGenerationA`](@ref) `Ftrip_Flag`."""
set_Ftrip_Flag!(value::AggregateDistributedGenerationA, val) = value.Ftrip_Flag = val
"""Set [`AggregateDistributedGenerationA`](@ref) `T_rv`."""
set_T_rv!(value::AggregateDistributedGenerationA, val) = value.T_rv = val
"""Set [`AggregateDistributedGenerationA`](@ref) `Trf`."""
set_Trf!(value::AggregateDistributedGenerationA, val) = value.Trf = val
"""Set [`AggregateDistributedGenerationA`](@ref) `dbd_pnts`."""
set_dbd_pnts!(value::AggregateDistributedGenerationA, val) = value.dbd_pnts = val
"""Set [`AggregateDistributedGenerationA`](@ref) `K_qv`."""
set_K_qv!(value::AggregateDistributedGenerationA, val) = value.K_qv = val
"""Set [`AggregateDistributedGenerationA`](@ref) `Tp`."""
set_Tp!(value::AggregateDistributedGenerationA, val) = value.Tp = val
"""Set [`AggregateDistributedGenerationA`](@ref) `T_iq`."""
set_T_iq!(value::AggregateDistributedGenerationA, val) = value.T_iq = val
"""Set [`AggregateDistributedGenerationA`](@ref) `D_dn`."""
set_D_dn!(value::AggregateDistributedGenerationA, val) = value.D_dn = val
"""Set [`AggregateDistributedGenerationA`](@ref) `D_up`."""
set_D_up!(value::AggregateDistributedGenerationA, val) = value.D_up = val
"""Set [`AggregateDistributedGenerationA`](@ref) `fdbd_pnts`."""
set_fdbd_pnts!(value::AggregateDistributedGenerationA, val) = value.fdbd_pnts = val
"""Set [`AggregateDistributedGenerationA`](@ref) `fe_lim`."""
set_fe_lim!(value::AggregateDistributedGenerationA, val) = value.fe_lim = val
"""Set [`AggregateDistributedGenerationA`](@ref) `P_lim`."""
set_P_lim!(value::AggregateDistributedGenerationA, val) = value.P_lim = val
"""Set [`AggregateDistributedGenerationA`](@ref) `dP_lim`."""
set_dP_lim!(value::AggregateDistributedGenerationA, val) = value.dP_lim = val
"""Set [`AggregateDistributedGenerationA`](@ref) `Tpord`."""
set_Tpord!(value::AggregateDistributedGenerationA, val) = value.Tpord = val
"""Set [`AggregateDistributedGenerationA`](@ref) `Kpg`."""
set_Kpg!(value::AggregateDistributedGenerationA, val) = value.Kpg = val
"""Set [`AggregateDistributedGenerationA`](@ref) `Kig`."""
set_Kig!(value::AggregateDistributedGenerationA, val) = value.Kig = val
"""Set [`AggregateDistributedGenerationA`](@ref) `I_max`."""
set_I_max!(value::AggregateDistributedGenerationA, val) = value.I_max = val
"""Set [`AggregateDistributedGenerationA`](@ref) `vl_pnts`."""
set_vl_pnts!(value::AggregateDistributedGenerationA, val) = value.vl_pnts = val
"""Set [`AggregateDistributedGenerationA`](@ref) `vh_pnts`."""
set_vh_pnts!(value::AggregateDistributedGenerationA, val) = value.vh_pnts = val
"""Set [`AggregateDistributedGenerationA`](@ref) `Vrfrac`."""
set_Vrfrac!(value::AggregateDistributedGenerationA, val) = value.Vrfrac = val
"""Set [`AggregateDistributedGenerationA`](@ref) `fl`."""
set_fl!(value::AggregateDistributedGenerationA, val) = value.fl = val
"""Set [`AggregateDistributedGenerationA`](@ref) `fh`."""
set_fh!(value::AggregateDistributedGenerationA, val) = value.fh = val
"""Set [`AggregateDistributedGenerationA`](@ref) `tfl`."""
set_tfl!(value::AggregateDistributedGenerationA, val) = value.tfl = val
"""Set [`AggregateDistributedGenerationA`](@ref) `tfh`."""
set_tfh!(value::AggregateDistributedGenerationA, val) = value.tfh = val
"""Set [`AggregateDistributedGenerationA`](@ref) `Tg`."""
set_Tg!(value::AggregateDistributedGenerationA, val) = value.Tg = val
"""Set [`AggregateDistributedGenerationA`](@ref) `rrpwr`."""
set_rrpwr!(value::AggregateDistributedGenerationA, val) = value.rrpwr = val
"""Set [`AggregateDistributedGenerationA`](@ref) `Tv`."""
set_Tv!(value::AggregateDistributedGenerationA, val) = value.Tv = val
"""Set [`AggregateDistributedGenerationA`](@ref) `Vpr`."""
set_Vpr!(value::AggregateDistributedGenerationA, val) = value.Vpr = val
"""Set [`AggregateDistributedGenerationA`](@ref) `Iq_lim`."""
set_Iq_lim!(value::AggregateDistributedGenerationA, val) = value.Iq_lim = val
"""Set [`AggregateDistributedGenerationA`](@ref) `V_ref`."""
set_V_ref!(value::AggregateDistributedGenerationA, val) = value.V_ref = val
"""Set [`AggregateDistributedGenerationA`](@ref) `Pfa_ref`."""
set_Pfa_ref!(value::AggregateDistributedGenerationA, val) = value.Pfa_ref = val
"""Set [`AggregateDistributedGenerationA`](@ref) `Q_ref`."""
set_Q_ref!(value::AggregateDistributedGenerationA, val) = value.Q_ref = val
"""Set [`AggregateDistributedGenerationA`](@ref) `P_ref`."""
set_P_ref!(value::AggregateDistributedGenerationA, val) = value.P_ref = val
"""Set [`AggregateDistributedGenerationA`](@ref) `base_power`."""
set_base_power!(value::AggregateDistributedGenerationA, val) = value.base_power = val
"""Set [`AggregateDistributedGenerationA`](@ref) `ext`."""
set_ext!(value::AggregateDistributedGenerationA, val) = value.ext = val


"""
MCMC iteration for pairwise alignment
"""
function simuiter(iter, p, q1, q2, L, tau, times, kappa, alpha, beta,
                  powera, dist, dist_min, best_match, match, thin, cut)

    match -= 1;
    best_match -= 1;

    Ixout = Array(Int64, 2*times);
    Ioriginal = Array(Int64, L+1);
    increment = 0.0; n_dist = 0.0; o_dist = 0.0; adjustcon = 0.0;
    ration = 0.0; prob = 0.0; u = 0.0; logpost = 0.0;
    increment_int = 0; newj = 0; tempnew = 0; tempold = 0; tempx = 0;
    newmatchvec = Array(Float64, 3);
    oldmatchvec = Array(Float64, 3);
    idenmatchvec = Array(Float64, 3);
    pnewvec = Array(Float64, 2);
    poldvec = Array(Float64, 2);
    internew = Array(Float64, 2*times);
    interold = Array(Float64, 2*times);
    qt1_5_interx = Array(Float64, 2*times);
    qt2_5_internew = Array(Float64, 2*times);
    qt2_5_interold = Array(Float64, 2*times);
    diff_ynew = Array(Float64, 2*times);
    diff_yold = Array(Float64, 2*times);
    original = Array(Float64, L+1);
    idy = Array(Float64, p);
    scalevec = Array(Float64, p);
    qt_5_fitted = Array(Float64, p);
    kappa_collect = Array(Float64, iter);
    log_collect = Array(Float64, iter);
    dist_collect = Array(Float64, iter);
    Irow = collect(1:p)-1;
    row = float(Irow);
    scale1 = Array(Float64, L);
    match_collect = Array(Float64, round(Integer,iter/thin), L+1);

    q2LLlen = (p-1)*times+1;
    q2LL = Array(Float64, q2LLlen);
    tempspan1 = collect(1:q2LLlen)-1;
    temp_span2 = float(tempspan1);
    timesf = float(times);
    q2LL_time = (temp_span2*(1/timesf));
    q2L_time1 = collect(1:p)-1;
    q2L_time2 = float(q2L_time1);
    q2LL = approx(q2L_time2, q2, q2LL_time);

    bar = Progress(iter, 1)   # minumum update interval: 1 second
    for j = 1:iter
        for i = 2:L
            if ((match[i+1]-match[i-1])>2)
                increment = rand(Normal(0, tau));
                increment_int = round(Integer,round(increment));
                if (increment_int == 0)
                    increment_int = (increment>0) ? 1 : -1;
                end
                newj = match[i] + increment_int;
                if (newj < match[i+1] && newj > match[i-1])
                    newmatchvec[1] = match[i-1];
                    newmatchvec[2] = newj;
                    newmatchvec[3] = match[i+1];
                    oldmatchvec[1] = match[i-1];
                    oldmatchvec[2] = match[i];
                    oldmatchvec[3] = match[i+1];
                    idenmatchvec[1] = times*(i-2);
                    idenmatchvec[2] = times*(i-1);
                    idenmatchvec[3] = times*i;
                    Ixout = collect(1:2*times)+times*(i-2)-1;
                    xout = float(Ixout);
                    internew = approx(idenmatchvec, newmatchvec, xout);
                    interold = approx(idenmatchvec, oldmatchvec, xout);
                    interx = copy(xout);
                    interynew = copy(internew);
                    push!(interynew, match[i+1]);
                    interyold = copy(interold);
                    push!(interyold, match[i+1]);
                    diff_ynew = interynew[2:2*times+1]-interynew[1:2*times];
                    diff_yold = interyold[2:2*times+1]-interyold[1:2*times];
                    for ll = 1:2*times
                        tempx = round(Integer, interx[ll]);
                        qt1_5_interx[ll] = q1[tempx+1];
                        internew[ll] = (internew[ll]>(p-1)) ? (p-1) : internew[ll];
                        tempnew = round(Integer, times*internew[ll]+1);
                        qt2_5_internew[ll] = q2LL[tempnew]*sqrt(diff_ynew[ll]);
                        interold[ll] = (interold[ll]>(p-1)) ? (p-1) : interold[ll];
                        tempold = round(Integer, times*interold[ll]+1);
                        qt2_5_interold[ll] = q2LL[tempold]*sqrt(diff_yold[ll]);
                    end
                    n_dist = norm(qt1_5_interx - qt2_5_internew)^2/p;
                    o_dist = norm(qt1_5_interx - qt2_5_interold)^2/p;
                    pnewvec = (newmatchvec[2:3] - newmatchvec[1:2])/p;
                    poldvec = (oldmatchvec[2:3] - oldmatchvec[1:2])/p;
                    adjustcon = exp((powera-1)*(sum(log(pnewvec))-
                                    sum(log(poldvec))));
                    ratio = adjustcon*exp(kappa*o_dist-kappa*n_dist);
                    prob = (ratio < 1) ? ratio : 1;
                    u = rand(Uniform(0,1));
                    match[i] = (u<prob) ? newj : match[i];
                end
            end
        end

        Ioriginal = (collect(1:L+1)-1)*times;
        original = float(Ioriginal);
        idy = round(approx(original, match, row));
        for ii = 1:L
            scale1[ii] = sqrt((match[ii+1]-match[ii])/times);
        end
        for kk = 1:p
            idy[kk] = (idy[kk]<p) ? idy[kk] : (p-1);
            scalevec[kk] = scale1[ceil(Integer,kk/times)];
            qt_5_fitted[kk] = scalevec[kk]*q2[round(Integer,idy[kk])+1];
        end
        dist = norm(q1-qt_5_fitted)^2/p;
        dist_collect[j] = dist;
        if (dist < dist_min)
            best_match = copy(match);
            dist_min = dist;
        end
        if (mod(j,thin)==0)
            match_collect[ceil(Integer, j/thin),:] = match;
        end
        kappa = rand(Gamma(p/2+alpha, 1/(dist+beta)));
        kappa_collect[j] = kappa;
        logpost = (p/2+alpha)*log(kappa)-kappa*(beta+dist);
        log_collect[j] = logpost;
        next!(bar);
    end
    match_collect += 1;
    best_match += 1;

    out = Dict("best_match" => best_match, "match_collect" => match_collect,
               "dist_collect" => dist_collect, "kappafamily" => kappa_collect,
               "log_posterior" => log_collect, "dist_min" => dist_min);
    return out
end

"""
MCMC iteration for group alignment
"""
function itermatch(iter, n, m, mu_5, match_matrix, qt_matrix,
                   qt_fitted_matrix, L, tau, times, kappa, alpha, beta,
                   powera, best_vec, dist_vec, best_match_matrix, mu_prior,
                   var_const, sumdist, thin, mu_q, mu_q_standard, logmax,
                   burnin, AVG)

    burnin -= 1;
    match_matrix -= 1;
    best_match_matrix -= 1;

    scale1 = Array(Float64, L);
    increment = 0.0; adjustcon = 0.0; ratio = 0.0; prob = 0.0; u = 0.0;
    n_dist = 0.0; o_dist = 0.0; dist = 0.0; logpost = 0.0; SigmaVar = 0.0;
    rescale = 0.0;
    increment_int = 0; newj = 0; tempnew = 0; tempold = 0; tempx = 0;
    newmatchvec = Array(Float64, 3);
    oldmatchvec = Array(Float64, 3);
    idenmatchvec = Array(Float64, 3);
    qt2_5 = Array(Float64, m);
    match = Array(Float64, L+1);
    interynew = Array(Float64, 2*times);
    interyold = Array(Float64, 2*times);
    interx = Array(Float64, 2*times);
    scalevec = Array(Float64, m);
    qt_5_fitted = Array(Float64, m);
    Ixout = Array(Int64, 2*times);
    Ioriginal = Array(Int64, L+1);
    xout = Array(Float64, 2*times);
    internew = Array(Float64, 2*times);
    interold = Array(Float64, 2*times);
    mu_5_interx = Array(Float64, 2*times);
    qt2_5_internew = Array(Float64, 2*times);
    qt2_5_interold = Array(Float64, 2*times);
    diff_ynew = Array(Float64, 2*times);
    diff_yold = Array(Float64, 2*times);
    pnewvec = Array(Float64, 2);
    poldvec = Array(Float64, 2);
    original = Array(Float64, L+1);
    idy = Array(Float64, m);
    Irow = collect(1:m)-1;
    row = float(Irow);
    karcher_res = Array(Float64, m+1);
    tempkarcher_res = Array(Float64, m+1);
    revscalevec = Array(Float64, L*times);
    kappa_collect = Array(Float64, iter);
    log_collect = Array(Float64, iter);
    MAP = Array(Float64, m);
    Mean = Array(Float64, m);
    qt2_5_warp = Array(Float64, m);
    mu_5_warp = Array(Float64, m);
    tempmatch_matrix = Array(Float64, L+1, n);
    bayes_warps = zeros(L+1, n);

    q2LLlen = (m-1)*times+1;
    q2LL_mat = Array(Float64, q2LLlen, n);
    tempspan1 = collect(1:q2LLlen)-1;
    temp_span2 = float(tempspan1);
    timesf = float(times);
    q2LL_time = (temp_span2*(1/timesf));
    q2L_time1 = collect(1:m)-1;
    q2L_time2 = float(q2L_time1);

    for LLL in 1:n
        q2LL_mat[:, LLL] = approx(q2L_time2, qt_matrix[:, LLL], q2LL_time);
    end

    bar = Progress(iter, 1);
    for j = 1:iter
        for t = 1:n
            qt2_5 = qt_matrix[:, t];
            match = match_matrix[:, t];
            for i = 2:L
                if ((match[i+1]-match[i-1])>2)
                    increment = rand(Normal(0, tau));
                    increment_int = round(Integer, round(increment));
                    if (increment_int == 0)
                        break
                        # increment_int = (increment>0)?(1):(-1);
                    end
                    newj = match[i] + increment_int;
                    if (newj < match[i+1] && newj > match[i-1])
                        newmatchvec[1] = match[i-1];
                        newmatchvec[2] = newj;
                        newmatchvec[3] = match[i+1];
                        oldmatchvec[1] = match[i-1];
                        oldmatchvec[2] = match[i];
                        oldmatchvec[3] = match[i+1];
                        idenmatchvec[1] = times*(i-2);
                        idenmatchvec[2] = times*(i-1);
                        idenmatchvec[3] = times*i;
                        Ixout = collect(1:2*times)+times*(i-2)-1;
                        xout = float(Ixout);
                        internew = approx(idenmatchvec, newmatchvec, xout);
                        interold = approx(idenmatchvec, oldmatchvec, xout);
                        interx = copy(xout);
                        interynew = copy(internew);
                        push!(interynew, match[i+1]);
                        interyold = copy(interold);
                        push!(interyold, match[i+1]);
                        diff_ynew = interynew[2:(2*times+1)]-interynew[1:2*times];
                        diff_yold = interyold[2:(2*times+1)]-interyold[1:2*times];
                        for ll = 1:2*times
                            tempx = round(Integer, interx[ll]);
                            mu_5_interx[ll] = mu_5[tempx+1];
                            internew[ll] = (internew[ll]>(m-1)) ? (m-1) : internew[ll];
                            tempnew = round(Integer, times*internew[ll]+1);
                            tempnew = (tempnew > q2LLlen) ? q2LLlen : tempnew;
                            qt2_5_internew[ll] = q2LL_mat[tempnew, t] *
                                sqrt(diff_ynew[ll]);
                            interold[ll] = (interold[ll]>(m-1)) ? (m-1) : interold[ll];
                            tempold = round(Integer, times*interold[ll]+1);
                            tempold = (tempold > q2LLlen) ? q2LLlen : tempold;
                            qt2_5_interold[ll] = q2LL_mat[tempold, t] *
                                sqrt(diff_yold[ll]);
                        end
                        n_dist = norm(mu_5_interx - qt2_5_internew)^2/m;
                        o_dist = norm(mu_5_interx - qt2_5_interold)^2/m;
                        pnewvec = (newmatchvec[2:3] - newmatchvec[1:2])/m;
                        poldvec = (oldmatchvec[2:3] - oldmatchvec[1:2])/m;
                        adjustcon = exp((powera-1)*(sum(log(pnewvec))-
                                        sum(log(poldvec))));
                        ratio = adjustcon*exp(kappa*o_dist-kappa*n_dist);
                        prob = (ratio < 1) ? ratio : 1;
                        u = rand(Uniform(0,1));
                        match[i] = (u<prob) ? newj : match[i];
                    end
                end
            end
            Ioriginal = (collect(1:L+1)-1)*times;
            original = float(Ioriginal);
            idy = approx(original, match, row);
            qt2_5_warp = approx(q2L_time2, qt2_5, idy);

            for ii = 1:L
                scale1[ii] = sqrt((match[ii+1]-match[ii])/times);
            end

            for kk = 1:m
                scalevec[kk] = scale1[ceil(Integer, kk/times)];
                qt_5_fitted[kk] = scalevec[kk]*qt2_5_warp[kk];
            end

            dist = norm(mu_5-qt_5_fitted)^2/m;
            match_matrix[:, t] = match;
            dist_vec[t] = dist;
            qt_fitted_matrix[:, t] = qt_5_fitted;
            if ((j/thin)>=burnin)
                bayes_warps[:, t] = bayes_warps[:, t] + match./AVG;
            end
        end
        sumdist[j] = sum(dist_vec);
        if (sumdist[j] < sum(best_vec))
            best_vec = copy(dist_vec);
            best_match_matrix = copy(match_matrix);
        end
        if (mod(j,thin)==0)
            mu_q[ceil(Integer, j/thin),:] = mu_5;
            karcher_res, revscalevec = findkarcherinv(match_matrix+1, times);
            mu_5_warp = approx(q2L_time2, vec(mu_5), karcher_res-1);
            for qq in 1:m
                mu_q_standard[ceil(Integer, j/thin), qq] = revscalevec[qq]*mu_5_warp[qq];
            end
        end
        kappa = rand(Gamma(n*m/2+alpha, 1/(sum(dist_vec)+beta)));
        kappa_collect[j] = kappa;
        logpost = (n*m/2+alpha)*log(kappa)-kappa*(beta+sum(dist_vec));
        log_collect[j] = logpost;
        if (logpost > logmax)
            logmax = logpost;
            MAP = copy(mu_5);
        end
        Mean = (var_const*m)/(m+2*kappa*n*var_const)*(2*kappa*n*mean(qt_fitted_matrix,2)/m + mu_prior/var_const);
        SigmaVar = (var_const*m)/(m+2*kappa*n*var_const);
        mu_5 = Mean + rand(Normal(0, sqrt(SigmaVar)), m);
        rescale = sqrt(m/sum(mu_5.^2));
        mu_5 *= rescale;
        next!(bar);
    end

    best_match_matrix += 1;
    bayes_warps += 1;

    out = Dict("mu_q_standard" => mu_q_standard, "mu_q" => mu_q,
               "best_match_matrix" => best_match_matrix,
               "sumdist" => sumdist, "kappafamily" => kappa_collect,
               "log_posterior" => log_collect, "dist" => sum(best_vec),
               "MAP" => MAP, "bayes_warps" => bayes_warps);
    return out
end


###############################################################################
#
# SoilPlantAirContinuum system for mono species system
#
###############################################################################
"""
    mutable struct SPACMono{FT}

Struct that mono species SoilPlantAirContinuum system.

# Fields
$(DocStringExtensions.FIELDS)
"""
Base.@kwdef mutable struct SPACMono{FT<:AbstractFloat}
    "Soil layers bounds `[m]`"
    soil_bounds::Array{FT,1} = FT[0,-0.1,-0.2,-0.3,-0.5,-0.8,-1.2,-2.0]
    "Air layers bounds `[m]`"
    air_bounds ::Array{FT,1} = collect(FT,0:1:20)

    "Root depth `[m]`"
    z_root  ::FT = FT(-1)
    "Canopy maximal height `[m]`"
    z_canopy::FT = FT(10)

    "Plant hydraulic system"
    plant_hs::AbstractPlantOrganism{FT} = create_grass(z_root, z_canopy,
                                                       soil_bounds, air_bounds)
    "Number of canopy layers"
    n_canopy::Int = length(plant_hs.canopy_index_in_air)
    "Number of root layers"
    n_root  ::Int = length(plant_hs.root_index_in_soil)
    "Plant photosynthesis systems"
    plant_ps::Array{CanopyLayer{FT},1} = [CanopyLayer{FT}() for
                                          i in 1:n_canopy]
    "Basal area `[m²]`"
    ba::FT = sum( [plant_hs.roots[i].area for i in 1:n_root] )
    "Ground area `[m²]`"
    ga::FT = ba * 500
    "Leaf area `[m²]`"
    la::FT = sum( plant_hs.leaves[i].area for i in 1:n_canopy )

    # Surrounding AirLayer
    "Air layers"
    envirs::Array{AirLayer{FT},1} = [AirLayer{FT}() for i in 1:n_canopy]

    # Wind related
    "Aerodynamic roughness `[m]`"
    wind_z0::FT = z_canopy * FT(0.07)
    "Zero plane displacement `[m]`"
    wind_d ::FT = z_canopy * 2/3
    "Mean layer height `[m]`"
    wind_zs::Array{FT,1} = [(air_bounds[i]+air_bounds[i+1])/2 for
                            i in 1:length(air_bounds)-1]
    "Wind speed per layer `[m s⁻¹]`"
    winds::Array{FT,1} = [FT(1) for i in 1:length(air_bounds)-1]

    # Soil layers information
    # TODO bridge Soil module later
    "Maximal soil water content"
    mswc  ::Array{FT,1} = [plant_hs.roots[i].sh.Θs for i in 1:n_root]
    "Current soil water content"
    swc   ::Array{FT,1} = [plant_hs.roots[i].sh.Θs for i in 1:n_root]
    "Array of soil matric potential `[MPa]`"
    p_soil::Array{FT,1} = FT[0 for i in 1:n_root]
    "Maximal soil depth `[m]`"
    h_soil::FT = abs(z_root)

    # geography related
    "Latitude `[°]`"
    latitude ::FT = 30
    "Longitude `[°]`"
    longitude::FT = 116
    "Elevation `[m]`"
    elevation::FT = 0

    # photosynthesis mode and stomatal model scheme
    "Photosynthesis parameter set"
    photo_set::AbstractPhotoModelParaSet{FT} = C3CLM(FT)
    "Stomatal behavior scheme"
    stomata_model::AbstractStomatalModel{FT} = ESMBallBerry{FT}()

    # For CanopyLayers module
    "Solar angle container"
    angles::SolarAngles{FT} = SolarAngles{FT}()
    "Canopy4RT container"
    canopy_rt::Canopy4RT{FT} = create_canopy_rt(FT, nLayer=n_canopy, LAI=la/ga)
    "Wave length container"
    wl_set::WaveLengths{FT} = create_wave_length(FT)
    "RT dimensions"
    rt_dim::CanopyLayers.RTDimensions = create_rt_dims(canopy_rt, wl_set);
    "CanopyRads container"
    can_rad::CanopyRads{FT} = create_canopy_rads(FT, rt_dim)
    "CanopyOpticals container"
    can_opt::CanopyOpticals{FT} = create_canopy_opticals(FT, rt_dim)
    "Array of LeafBios container"
    leaves_rt::Array{LeafBios{FT},1} = [create_leaf_bios(FT, rt_dim) for
                                         i in 1:n_canopy]
    "SoilOpticals container"
    soil_opt::SoilOpticals{FT} = create_soil_opticals(wl_set)
    "Incoming radiation container"
    in_rad::IncomingRadiation{FT} = create_incoming_radiation(wl_set)
    "RT container"
    rt_con::RTCache{FT} = create_rt_cache(FT, rt_dim)
    "Container for sunlit leaf area fraction in each layer"
    f_SL::Array{FT,1} = repeat(canopy_rt.lidf, outer=[ canopy_rt.nAzi ]) /
                        canopy_rt.nAzi;

    # local storage for canopy GPP and NPP
    "Canopy GPP per ground area"
    f_gpp::FT = 0
    "Canopy GPP per ground area"
    f_npp::FT = 0
    "Canopy water flux per ground area"
    f_H₂O::FT = 0
end


# This file is part of EndemicModel project, which is licensed under BDS-3-Clause.
# See file LICENSE.md for more information.

function database_path(
    ext::Union{Nothing,AbstractString} = data_extension(option(:database_data_type));
    database_directory = option(:database_directory),
    database_filename = option(:database_filename),
    kwargs...,
)
    joinpath(Path(database_directory), Path(database_filename * "." * ext))
end

function database_path(sourcesym::Symbol; kwargs...)
    source = DATA_SOURCES[sourcesym]
    database_path(data_extension(source); kwargs...)
end

function import_data(
    database::AbstractDatabase;
    database_data_type = option(:database_data_type),
    database_directory = option(:database_directory),
    database_filename = name(database),
    model_data_type = option(:model_data_type),
    model_directory = option(:model_directory),
    model_filename = name(database),
    simple::Bool = true,
    force_update::Bool = false,
    kwargs...,
)
    @debug "Creating database."
    imported = false
    if !force_update
        db_input = database_path(
            database_data_type;
            database_directory = database_directory,
            database_filename = database_filename,
        )
        model_input = database_path(
            model_data_type;
            database_directory = model_directory,
            database_filename = database_filename,
        )
        if (
            exists(db_input) &&
            exists(model_input) &&
            modified(db_input) >= today() &&
            modified(model_input) >= today()
        )
            @debug "Importing data from cache." db_input model_input
            data =
                import_data(database_data_type; path = db_input, simple = simple, kwargs...)
            modeldata = import_data(
                database_data_type;
                path = model_input,
                simple = simple,
                kwargs...,
            )
            model = SEIRModel(data; modeldata = modeldata, kwargs...)
            imported = true
            @debug "Data imported from cache."
        end
    end
    if !imported
        @debug "Importing data from sources."
        imp_kwargs = merge(default_kwargs(database), kwargs)
        data = import_data(sources(database); imp_kwargs...)
        model = nothing
        funcs = computing_functions(database)

        @debug "Data imported from sources." _debuginfo(data)
        @debug "Computing database."
        while any(func(data) for func ∈ funcs)
        end
    end
    @debug "Database created."
    (data, model)
end

function import_data!(database::AbstractDatabase; kwargs...)
    (data, model) = import_data(database; kwargs...)
    datadict!(database, data)
    !isnothing(model) && modeldict!(database, model)
    (data, model)
end

function export_data(source::AbstractDataSource, database::AbstractDatabase; kwargs...)
    export_data(source, datadict(database); kwargs...)
end

function export_data(
    database::AbstractDatabase;
    database_data_type = option(:database_data_type),
    database_directory = option(:database_directory),
    database_filename = name(database),
    model_data_type = option(:model_data_type),
    model_directory = option(:model_directory),
    model_filename = name(database),
    pretty::Bool = true,
    kwargs...,
)
    db_output = database_path(
        database_data_type;
        database_directory = database_directory,
        database_filename = database_filename,
    )
    model_output = database_path(
        model_data_type;
        database_directory = model_directory,
        database_filename = database_filename,
    )
    data = datadict(database)
    @info "Exporting database." path = db_output _debuginfo(data)
    dbout = export_data(
        database_data_type,
        data;
        pretty = pretty,
        output = db_output,
        kwargs...,
    )
    data = modeldict(database)
    @info "Exporting database models." path = model_output _debuginfo(data)
    modelout = export_data(
        model_data_type,
        data;
        pretty = pretty,
        output = model_output,
        kwargs...,
    )
    (dbout, modelout)
end


function GLONASSNumericalPropagator(arg0::GLONASSNumericalPropagator_Builder)
    return GLONASSNumericalPropagator((GLONASSNumericalPropagator_Builder,), arg0)
end

function add_additional_equations(obj::AbstractIntegratedPropagator, arg0::AdditionalEquations)
    return jcall(obj, "addAdditionalEquations", void, (AdditionalEquations,), arg0)
end

function add_additional_state_provider(obj::AbstractPropagator, arg0::AdditionalStateProvider)
    return jcall(obj, "addAdditionalStateProvider", void, (AdditionalStateProvider,), arg0)
end

function add_event_detector(obj::AbstractIntegratedPropagator, arg0::EventDetector)
    return jcall(obj, "addEventDetector", void, (EventDetector,), arg0)
end

function clear_events_detectors(obj::AbstractIntegratedPropagator)
    return jcall(obj, "clearEventsDetectors", void, ())
end

function get_additional_state_providers(obj::AbstractPropagator)
    return jcall(obj, "getAdditionalStateProviders", List, ())
end

function get_attitude_provider(obj::AbstractPropagator)
    return jcall(obj, "getAttitudeProvider", AttitudeProvider, ())
end

function get_basic_dimension(obj::AbstractIntegratedPropagator)
    return jcall(obj, "getBasicDimension", jint, ())
end

function get_calls(obj::AbstractIntegratedPropagator)
    return jcall(obj, "getCalls", jint, ())
end

function get_events_detectors(obj::AbstractIntegratedPropagator)
    return jcall(obj, "getEventsDetectors", Collection, ())
end

function get_frame(obj::AbstractPropagator)
    return jcall(obj, "getFrame", Frame, ())
end

function get_generated_ephemeris(obj::AbstractIntegratedPropagator)
    return jcall(obj, "getGeneratedEphemeris", BoundedPropagator, ())
end

function get_glonass_orbital_elements(obj::GLONASSNumericalPropagator)
    return jcall(obj, "getGLONASSOrbitalElements", GLONASSOrbitalElements, ())
end

function get_initial_state(obj::AbstractPropagator)
    return jcall(obj, "getInitialState", SpacecraftState, ())
end

function get_managed_additional_states(obj::AbstractIntegratedPropagator)
    return jcall(obj, "getManagedAdditionalStates", Vector{JString}, ())
end

function get_mode(obj::AbstractPropagator)
    return jcall(obj, "getMode", jint, ())
end

function get_mu(obj::AbstractIntegratedPropagator)
    return jcall(obj, "getMu", jdouble, ())
end

function get_pv_coordinates(obj::AbstractPropagator, arg0::AbsoluteDate, arg1::Frame)
    return jcall(obj, "getPVCoordinates", TimeStampedPVCoordinates, (AbsoluteDate, Frame), arg0, arg1)
end

function get_pv_in_pz90(obj::GLONASSNumericalPropagator, arg0::SpacecraftState)
    return jcall(obj, "getPVInPZ90", PVCoordinates, (SpacecraftState,), arg0)
end

function is_additional_state_managed(obj::AbstractIntegratedPropagator, arg0::JString)
    return jcall(obj, "isAdditionalStateManaged", jboolean, (JString,), arg0)
end

function propagate(obj::AbstractIntegratedPropagator, arg0::AbsoluteDate, arg1::AbsoluteDate)
    return jcall(obj, "propagate", SpacecraftState, (AbsoluteDate, AbsoluteDate), arg0, arg1)
end

function propagate(obj::GLONASSNumericalPropagator, arg0::AbsoluteDate)
    return jcall(obj, "propagate", SpacecraftState, (AbsoluteDate,), arg0)
end

function reset_initial_state(obj::AbstractPropagator, arg0::SpacecraftState)
    return jcall(obj, "resetInitialState", void, (SpacecraftState,), arg0)
end

function set_attitude_provider(obj::AbstractIntegratedPropagator, arg0::AttitudeProvider)
    return jcall(obj, "setAttitudeProvider", void, (AttitudeProvider,), arg0)
end

function set_ephemeris_mode(obj::AbstractIntegratedPropagator)
    return jcall(obj, "setEphemerisMode", void, ())
end

function set_ephemeris_mode(obj::AbstractIntegratedPropagator, arg0::OrekitStepHandler)
    return jcall(obj, "setEphemerisMode", void, (OrekitStepHandler,), arg0)
end

function set_master_mode(obj::AbstractIntegratedPropagator, arg0::OrekitStepHandler)
    return jcall(obj, "setMasterMode", void, (OrekitStepHandler,), arg0)
end

function set_master_mode(obj::AbstractPropagator, arg0::jdouble, arg1::OrekitFixedStepHandler)
    return jcall(obj, "setMasterMode", void, (jdouble, OrekitFixedStepHandler), arg0, arg1)
end

function set_mu(obj::AbstractIntegratedPropagator, arg0::jdouble)
    return jcall(obj, "setMu", void, (jdouble,), arg0)
end

function set_reset_at_end(obj::AbstractIntegratedPropagator, arg0::jboolean)
    return jcall(obj, "setResetAtEnd", void, (jboolean,), arg0)
end

function set_slave_mode(obj::AbstractIntegratedPropagator)
    return jcall(obj, "setSlaveMode", void, ())
end


using IL_simulations, PDBTools

top_dir     =  "/home/viniciusp/Documents/doutorado/ANALYSE/Repository/IL_simulations.jl/VSIL/ITP"
pdb_dir     =  "/home/viniciusp/Documents/doutorado/ANALYSE/Repository/IL_simulations.jl/VSIL/PDB"
input_dir   =  "/home/viniciusp/Documents/doutorado/ANALYSE/Repository/IL_simulations.jl/ubiquitin_files"
top_ion_dir =  "/home/viniciusp/Documents/doutorado/ANALYSE/Repository/IL_simulations.jl/ion_files"

atoms   = readPDB("$input_dir/ubq.pdb")
MMP     = mass(atoms)

cation1 = "BMIM"
anion1  = "NO3"
anion2  = "DCA"

function molarmass(anion, cation, pdb_dir)
  atcation = readPDB("$pdb_dir/$(cation)_VSIL.pdb")
  atanion  = readPDB("$pdb_dir/$(anion)_VSIL.pdb")
  MMIL = mass(atcation) + mass(atanion)
  return MMIL
end

MMIL  = molarmass(anion1, cation1, pdb_dir)
MMIL2 = molarmass(anion2, cation1, pdb_dir)
c       =  0.10

## test with one IL/salt and a protein/polymer
#cd("PONEIL")
#newil = EMIMCL

cation = "EMIM"
anion  = "Br"
MMIL = molarmass(anion, cation, pdb_dir)
dir = pwd();
for i in ["0.50", "1.00", "1.50", "2.00", "2.50", "3.00"]
  for j in ["00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19","20"]
    cd("$dir/EMIMBr/$i/$j")
    conc = parse(Float64,i)
    data = IL_simulations.Data_il(protein="$input_dir/ubq.pdb", MMP = MMP, cation = cation, anion = anion,MM = MMIL,c=conc)
    nions, nwater, sides = prot_elec(data)
    IL_simulations.top(dict, top_dir, input_dir, data, nions, nwater)
    IL_simulations.pack_input(data,pdb_dir,nions,nwater,sides)
    IL_simulations.mdp_files_solute()
  end
end

#println("PONEIL")
## test with one IL/salt
#cd("../ONEIL")
#data = IL_simulations.Data_elec(cation = cation1, anion = anion1, MM = MMIL,c=c)
#nions, nwater, sides = sol_elec(data)
#IL_simulations.topelec(dict, top_dir, top_ion_dir, data, nions, nwater)
#IL_simulations.pack_input_sol(data,pdb_dir,nions,nwater,sides)
#IL_simulations.mdp_files()

#println("ONEIL")

# test with two ILs/salts and a protein/polymer -same cation
#cd("../PTWOIL")
#data = IL_simulations.Data_2il(protein="$input_dir/ubq.pdb", MMP = MMP, cation = cation1, anion1 = anion1, anion2 = anion2, MM1 = MMIL, MM2 = MMIL2,c=c)
#ncat, nan, nwater, sides = prot_melec(data)
#IL_simulations.top(dict, top_dir, input_dir, data, ncat, nan, nwater)
#IL_simulations.pack_input(data, pdb_dir, ncat, nan, nwater, sides)
#IL_simulations.mdp_files()

#println("PTWOIL")

### test with two ILs/salts - same cation
#cd("../TWOIL")
#data = IL_simulations.Data_twoelec(cation = cation1, anion1 = anion1, anion2 = anion2 , MM1 = MMIL, MM2 = MMIL2, c=c)
#ncat, nan, nwater, sides = sol_melec(data)
#IL_simulations.topelec(dict,top_dir, top_ion_dir, data, ncat, nan, nwater)
#IL_simulations.pack_input_elec(data, pdb_dir, ncat, nan, nwater, sides)
#IL_simulations.mdp_files()
#
#
#println("TWOIL")


using Compat

"""
Any entity that is tracked through the pooled experiment. For CRISPR screens,
this is equivalent to the sgRNA. This object stores properties relating to the
performance of this entity in the screen.

$(FIELDS)
"""
type Barcode
    "The target gene id"
    gene::Int

    "The knockdown efficiency"
    knockdown::Float64

    "The theoretical phenotype exerted"
    theo_phenotype::Float64

    "The observed phenotype (only relevant for FACS screens)"
    obs_phenotype::Float64

    "The gene behavior, either sigmoidal or linear"
    behavior::Symbol

    "The gene activity class, either inactive, up or down"
    class::Symbol

    "Initial frequency after transfection"
    initial_freq::Float64

    function Barcode(gene, knockdown, phenotype, behavior, class)
        if (0 <= knockdown <= 1)
            new(gene, knockdown, phenotype, NaN, behavior, class, -Inf)
        else
            error("Knockdown must be between 0 and 1, inclusive")
        end
    end
end

as_array(bc::Barcode) = [getfield(bc, fld) for fld in fieldnames(bc) if fld != :obs_phenotype]

"""
A description of screen parameters
"""
@compat abstract type ScreenSetup end

as_array(ss::ScreenSetup) = [getfield(ss, fld) for fld in fieldnames(ss)]

"""
A type representing the parameters used in a typical FACS screen.

$(FIELDS)
"""
type FacsScreen <: ScreenSetup
    "Number of genes targeted by the screen"
    num_genes::Int

    "Number of guides per gene"
    coverage::Int

    """Number of cells with each guide. `representation=10` means that there are
    10 times as many cells as guides during transfection. The actual number of
    cells per guide post-transfection will be less depending on the MOI"""
    representation::Int

    """The multiplicity of infection, ``\\lambda``, of the screen. We model this as a Poisson
    process during transfection (see [`Simulation.transfect`](@ref)).

    !!! note

        We **do not** model multiple infections. We assume that the MOI is properly
        selected and less than half the cells are transfected by any virus, i.e.
        ``\\lambda \\lt 0.5`` and then select only the cells that have a single
        transfection occurrence:

        ```math
        P(x = 1; Poisson(λ))
        ```

        For ``\\lambda = 0.25`` this works out to being ``\\approx 19.5\\%`` of the
        number of cells (`num_genes` * `coverage` * `representation`).
    """
    moi::Float64

    """The standard deviation expected for cells during FACS sorting. This should
    be set according to the biological variance experimentally observed, e.g. in
    the fluorescence intensity of isogenic cells"""
    σ::Float64

    """Range of guide phenotypes to collect in each bin

    # Example

    In the following example

    ```julia
    p = 0.05
    bin_info = Dict(:bin1 => (0.0, p), :bin2 => (1.0-p, 1.0))
    ```

    The 5th percentile of cells sorted according to their phenotype (fluorescence,
    size, etc) will be compared to the 95th percentile.
    """
    bin_info::Associative{Symbol, Tuple{Float64, Float64}}

    "Number of cells sorted expressed as an integer multiple of the number of guides"
    bottleneck_representation::Int

    """Sequencing depth as a integer multiple of the number of guides, i.e.
    `seq_depth=10` is equivalent to `10 * num_genes * coverage` reads.
    """
    seq_depth::Int

    function FacsScreen()
        new(500, 5, 100, 0.25, 1.0, OrderedDict(:bin1 => (0.0, 1/3), :bin2 => (2/3, 1.0)), 1000, 1000)
    end
end

"""
A type representing the parameters used in a typical growth-based screen.

$(FIELDS)
"""
type GrowthScreen <: ScreenSetup
    "Number of genes targeted by the screen"
    num_genes::Int

    "Number of guides per gene"
    coverage::Int

    """Number of cells with each guide. `representation=10` means that there are
    10 times as many cells as guides during transfection. The actual number of
    cells per guide post-transfection will be less depending on the MOI"""
    representation::Int

    """The multiplicity of infection, ``\\lambda``, of the screen. We model this as a Poisson
    process during transfection (see [`Simulation.transfect`](@ref)).

    !!! note

        We **do not** model multiple infections. We assume that the MOI is properly
        selected and less than half the cells are transfected by any virus, i.e.
        ``\\lambda \\lt 0.5`` and then select only the cells that have a single
        transfection occurrence:

        ```math
        P(x = 1; Poisson(λ))
        ```

        For ``\\lambda = 0.25`` this works out to being ``\\approx 19.5\\%`` of the
        number of cells (`num_genes` * `coverage` * `representation`).
    """
    moi::Float64

    """Sequencing depth as a integer multiple of the number of guides, i.e.
    `seq_depth=10` is equivalent to `10 * num_genes * coverage` reads.
    """
    seq_depth::Int

    """For growth screens, how much of a bottleneck is applied. This is minimum
    number of cells that is kept when passaging the pool of cells. This is
    expressed as an integer multiple of the number of guides."""
    bottleneck_representation::Int

    """For growth screens, how many bottlenecks are applied. This is the integer
    number of passages during the growth screen."""
    num_bottlenecks::Int

    """Before each passage, the theoretical phenotype of each cell is convolved
    with a normal noise distribution with a standard deviation, σ, dictated by
    this parameter. This should be set based on an expectation of noisiness of
    the subsampling"""
    noise::Float64

    function GrowthScreen()
        new(500, 5, 100, 0.25, 1000, 1000, 10, 0.01)
    end
end

function _print(io::IO, s::ScreenSetup)
    print(io, typeof(s), " <: ScreenSetup \n")

    info = (
        "Number of genes: $(s.num_genes)",
        "Number of guides per gene: $(s.coverage) ($(s.num_genes*s.coverage) guides total)",
        "Cells per guide at transfection: $(s.representation) ($(s.num_genes*s.coverage*s.representation) cells total)",
        "Multiplicity of infection (M.O.I.): $(s.moi)",
        "Number of cells per guide at sequencing: $(s.seq_depth) ($(s.num_genes*s.coverage*s.seq_depth) cells total)",
    )

    for line in info
        print(io, "\n    ", line)
    end
end

function Base.show(io::IO, s::FacsScreen)
    _print(io::IO, s)

    info = (
        "",
        "Number of cells per guide sorted: $(s.bottleneck_representation) ($(s.num_genes*s.coverage*s.bottleneck_representation) cells total)",
        "Gaussian standard deviation of sorting: $(s.σ) (phenotype units)",
        "Bins:"
    )

    for line in info
        print(io, "\n    ", line)
    end

    for (bin, range) in s.bin_info
        print(io, "\n        $bin: $(round(Int, range[1]*100))-$(round(Int, range[2]*100)) percentile of cells",)
    end
end

function Base.show(io::IO, s::GrowthScreen)
    _print(io::IO, s)

    info = (
        "",
        "Minimum number of cells kept per subsampling: $(s.bottleneck_representation) ($(s.num_genes*s.coverage*s.bottleneck_representation) cells total)",
        "Number of rounds of subsampling: $(s.num_bottlenecks)",
        "Gaussian standard deviation of subsampling: $(s.noise) (phenotype units)",
    )

    for line in info
        print(io, "\n    ", line)
    end
end


# Assuming dev, net, feats, ndeps are predefined
eqparse(a,b)=(isequal(a[1],b[1]), isequal(sortcols(vcat(a[2],a[3])), sortcols(vcat(b[2],b[3]))))
gnet = copy(net, :gpu)
gnet[end].f = KUnet.logp

isdefined(:g0) || @date g0=KUparser.gparse(dev,gnet,feats,ndeps)
isdefined(:g1) || @date g1=KUparser.gparse(dev,gnet,feats,ndeps,1700)
@show eqparse(g1,g0)
isdefined(:g2) || @date g2=KUparser.gparse(dev,gnet,feats,ndeps,1700,20)
@show eqparse(g2,g0)
isdefined(:g3) || @date g3=KUparser.gparse(dev,gnet,feats,ndeps,100)
@show eqparse(g3,g0)
isdefined(:g4) || @date g4=KUparser.gparse(dev,gnet,feats,ndeps,100,20)
@show eqparse(g4,g0)
isdefined(:g100) || @date g100=KUparser.gparse(dev[1:100],gnet,feats,ndeps,100,20)

if false

isdefined(:b0) || @date b0=KUparser.bparse(dev,gnet,feats,ndeps,1)
@show eqparse(b0,g0)
isdefined(:b1) || @date b1=KUparser.bparse(dev,gnet,feats,ndeps,1,1700)
@show eqparse(b1,g0)
isdefined(:b2) || @date b2=KUparser.bparse(dev,gnet,feats,ndeps,1,1700,20)
@show eqparse(b2,g0)
isdefined(:b3) || @date b3=KUparser.bparse(dev,gnet,feats,ndeps,1,100)
@show eqparse(b3,g0)
isdefined(:b4) || @date b4=KUparser.bparse(dev,gnet,feats,ndeps,1,100,20)
@show eqparse(b4,g0)

isdefined(:b01) || @date b01=KUparser.bparse(dev[1:100],gnet,feats,ndeps,10)
@show eqparse(b01,g100)
isdefined(:b11) || @date b11=KUparser.bparse(dev[1:100],gnet,feats,ndeps,10,100)
@show eqparse(b11,b01)
isdefined(:b51) || @date b51=KUparser.bparse(dev[1:100],gnet,feats,ndeps,10,5)
@show eqparse(b51,b01)
isdefined(:b21) || @date b21=KUparser.bparse(dev[1:100],gnet,feats,ndeps,10,5,20)
@show eqparse(b21,b01)
isdefined(:b31) || @date b31=KUparser.bparse(dev[1:100],gnet,feats,ndeps,10,1)
@show eqparse(b31,b01)
isdefined(:b41) || @date b41=KUparser.bparse(dev[1:100],gnet,feats,ndeps,10,1,20)
@show eqparse(b41,b01)

isdefined(:b02) || @date b02=KUparser.bparse(dev[1:100],gnet,feats,ndeps,100)
@show eqparse(b02,g100)
@show eqparse(b02,b01)
isdefined(:b12) || @date b12=KUparser.bparse(dev[1:100],gnet,feats,ndeps,100,100)
@show eqparse(b12,b02)
isdefined(:b52) || @date b52=KUparser.bparse(dev[1:100],gnet,feats,ndeps,100,5)
@show eqparse(b52,b02)
isdefined(:b22) || @date b22=KUparser.bparse(dev[1:100],gnet,feats,ndeps,100,5,20)
@show eqparse(b22,b02)
isdefined(:b32) || @date b32=KUparser.bparse(dev[1:100],gnet,feats,ndeps,100,1)
@show eqparse(b32,b02)
isdefined(:b42) || @date b42=KUparser.bparse(dev[1:100],gnet,feats,ndeps,100,1,20)
@show eqparse(b42,b02)

end


using PyCall
using CSV
using CodecZlib, Mmap
using JLD2

const DEFAULT_FEATURE_COLS = [:ensembleid, :genesymbol, :type]
const DEFAULT_BARCODE_COLS = [:barcode]
const FEATURE_COLS = [:featurekey, :featurename, :featuretype, :chromosome, :featurestart, :featureend, :isgene, :genus_species]

function read_mtx(filename::String)
    py"""
    from scipy.io import mmread
    mtx = mmread($filename)
    """
    sparse(py"mtx.row" .+ 1, py"mtx.col" .+ 1, py"mtx.data")
end

function read_tsv_gz(filename::String, header::Vector{Symbol}=Symbol[])
    gz_unzipper = transcode(GzipDecompressor, Mmap.mmap(filename))
    if isempty(header)
        return CSV.File(gz_unzipper; delim='\t') |> DataFrame
    else
        return CSV.File(gz_unzipper; delim='\t', header=header) |> DataFrame
    end
end

read_features(filename::String, names=DEFAULT_FEATURE_COLS) = read_tsv_gz(filename, names)
read_barcodes(filename::String, names=DEFAULT_BARCODE_COLS) = read_tsv_gz(filename, names)

## Human Cell Atlas (HCA)
read_genes(filename::String) = read_tsv_gz(filename)
read_cells(filename::String) = read_tsv_gz(filename)


using Distributed
@everywhere using Dates
@everywhere using DelimitedFiles
@everywhere using CSV
#@everywhere using Plots
@everywhere using Statistics
@everywhere using DocStringExtensions
@everywhere using DataFrames
@everywhere using Random

@everywhere module_dir = "/home/sarah/HBVModel/src/"
@everywhere push!(LOAD_PATH, $module_dir)

# load list of structs
@everywhere include(module_dir*"Model_Core/structs.jl")
# load components of models represented by buckets
@everywhere include(module_dir*"Model_Core/processes_buckets.jl")
# load functions that combine all components of one HRU
@everywhere include(module_dir*"Model_Core/elevations.jl")
# load functions for combining all HRUs and for running the model
@everywhere include(module_dir*"Model_Core/allHRU.jl")
# load function for running model which just returns the necessary output for calibration
@everywhere include(module_dir*"Model_Core/run_model.jl")
# load functions for preprocessing temperature and precipitation data
@everywhere include(module_dir*"Model_Core/Preprocessing.jl")
# load functions for calculating the potential evaporation
@everywhere include(module_dir*"Model_Core/Potential_Evaporation.jl")
# load objective functionsM
@everywhere include(module_dir*"Model_Core/ObjectiveFunctions.jl")
# load parameterselection
@everywhere include(module_dir*"Model_Core/parameterselection.jl")
# load running model in several precipitation zones
@everywhere include(module_dir*"Model_Core/runmodel_Prec_Zones.jl")

@everywhere function run_MC(ID, nmax)

        local_path = "/home/sarah/"
        # ------------ CATCHMENT SPECIFIC INPUTS----------------
        ID_Prec_Zones = [17700, 114926]
        # size of the area of precipitation zones
        Area_Zones = [235811198.0, 31497403.0]
        Area_Catchment = sum(Area_Zones)
        Area_Zones_Percent = Area_Zones / Area_Catchment
        Snow_Threshold = 600
        Height_Threshold = 2700

        Mean_Elevation_Catchment =  2300 # in reality 2233.399986
        Elevations_Catchment = Elevations(200.0, 1000.0, 3600.0, 1385., 1385.) # take temp at 17700
        Sunhours_Vienna = [8.83, 10.26, 11.95, 13.75, 15.28, 16.11, 15.75, 14.36, 12.63, 10.9, 9.28, 8.43]
        # where to skip to in data file of precipitation measurements
        Skipto = [0, 24]
        # get the areal percentage of all elevation zones in the HRUs in the precipitation zones
        Areas_HRUs =  CSV.read(local_path*"HBVModel/Defreggental/HBV_Area_Elevation_round.csv", skipto=2, decimal='.', delim = ',')
        # get the percentage of each HRU of the precipitation zone
        Percentage_HRU = CSV.read(local_path*"HBVModel/Defreggental/HRU_Prec_Zones.csv", header=[1], decimal='.', delim = ',')
        Elevation_Catchment = convert(Vector, Areas_HRUs[2:end,1])
        startyear = 1983
        endyear = 2005
        scale_factor_Discharge = 0.65
        # timeperiod for which model should be run (look if timeseries of data has same length)
        Timeseries = collect(Date(startyear, 1, 1):Day(1):Date(endyear,12,31))
        #------------ TEMPERATURE AND POT. EVAPORATION CALCULATIONS ---------------------

        Temperature = CSV.read(local_path*"HBVModel/Defreggental/prenner_tag_17700.dat", header = true, skipto = 3, delim = ' ', ignorerepeated = true)

        # get data for 20 years: from 1987 to end of 2006
        # from 1986 to 2005 13669: 20973
        #hydrological year 13577:20881
        Temperature = dropmissing(Temperature)
        Temperature_Array = Temperature.t / 10
        Precipitation_17700 = Temperature.nied / 10
        Timeseries_Temp = Date.(Temperature.datum, Dates.DateFormat("yyyymmdd"))
        startindex = findfirst(isequal(Date(startyear, 1, 1)), Timeseries_Temp)
        endindex = findfirst(isequal(Date(endyear, 12, 31)), Timeseries_Temp)
        Temperature_Daily = Temperature_Array[startindex[1]:endindex[1]]
        Dates_Temperature_Daily = Timeseries_Temp[startindex[1]:endindex[1]]
        Precipitation_17700 = Precipitation_17700[startindex[1]:endindex[1]]
        Precipitation_17700[findall(x -> x == -0.1, Precipitation_17700)] .= 0.0

        #
        # Dates_Temperature_Daily = Timeseries_Temp[startindex[1]:endindex[1]]
        # Dates_missing_Temp = Dates_Temperature_Daily[findall(x-> x == 999.9, Temperature_Daily)]
        # # get the temperature data at each elevation
        Elevation_Zone_Catchment, Temperature_Elevation_Catchment, Total_Elevationbands_Catchment = gettemperatureatelevation(Elevations_Catchment, Temperature_Daily)
        # # get the temperature data at the mean elevation to calculate the mean potential evaporation
        Temperature_Mean_Elevation = Temperature_Elevation_Catchment[:,findfirst(x-> x==Mean_Elevation_Catchment, Elevation_Zone_Catchment)]
        Potential_Evaporation = getEpot_Daily_thornthwaite(Temperature_Mean_Elevation, Dates_Temperature_Daily, Sunhours_Vienna)
        # ------------ LOAD OBSERVED DISCHARGE DATA ----------------
        Discharge = CSV.read(local_path*"HBVModel/Defreggental/Q-Tagesmittel-212100.csv", header= false, skipto=26, decimal=',', delim = ';', types=[String, Float64])
        Discharge = convert(Matrix, Discharge)
        startindex = findfirst(isequal("01.01."*string(startyear)*" 00:00:00"), Discharge)
        endindex = findfirst(isequal("31.12."*string(endyear)*" 00:00:00"), Discharge)
        Observed_Discharge = Array{Float64,1}[]
        push!(Observed_Discharge, Discharge[startindex[1]:endindex[1],2])
        Observed_Discharge = Observed_Discharge[1]
        Observed_Discharge = Observed_Discharge * 1000 / Area_Catchment * (3600 * 24)

        # ------------ LOAD TIMESERIES DATA AS DATES ------------------
        #Timeseries = Date.(Discharge[startindex[1]:endindex[1],1], Dates.DateFormat("d.m.y H:M:S"))
        firstyear = Dates.year(Timeseries[1])
        lastyear = Dates.year(Timeseries[end])

        # ------------- LOAD OBSERVED SNOW COVER DATA PER PRECIPITATION ZONE ------------
        # # find day wehere 2000 starts for snow cover calculations
        start2000 = findfirst(x -> x == Date(2000, 01, 01), Timeseries)
        length_2000_end = length(Observed_Discharge) - start2000 + 1
        observed_snow_cover = Array{Float64,2}[]
        for ID in ID_Prec_Zones
                current_observed_snow = readdlm(local_path*"HBVModel/Defreggental/snow_cover_fixed_Zone"*string(ID)*".csv",',', Float64)
                current_observed_snow = current_observed_snow[1:length_2000_end,3: end]
                push!(observed_snow_cover, current_observed_snow)
        end
        # ------------- LOAD PRECIPITATION DATA OF EACH PRECIPITATION ZONE ----------------------
        # get elevations at which precipitation was measured in each precipitation zone
        Elevations_17700 = Elevations(200., 1200., 3600., 1385., 1140)
        Elevations_114926 = Elevations(200, 1000, 2800, 1110., 1140)
        Elevations_All_Zones = [Elevations_17700, Elevations_114926]

        #get the total discharge
        Total_Discharge = zeros(length(Temperature_Daily))
        Inputs_All_Zones = Array{HRU_Input, 1}[]
        Storages_All_Zones = Array{Storages, 1}[]
        Precipitation_All_Zones = Array{Float64, 2}[]
        Precipitation_Gradient = 0.0
        Elevation_Percentage = Array{Float64, 1}[]
        Nr_Elevationbands_All_Zones = Int64[]
        Elevations_Each_Precipitation_Zone = Array{Float64, 1}[]
        Glacier_All_Zones = Array{Float64, 2}[]

        for i in 1: length(ID_Prec_Zones)
                if ID_Prec_Zones[i] == 114926
                        #print(ID_Prec_Zones[i])
                        Precipitation = CSV.read(local_path*"HBVModel/Defreggental/N-Tagessummen-"*string(ID_Prec_Zones[i])*".csv", header= false, skipto=Skipto[i], missingstring = "L\xfccke", decimal=',', delim = ';')
                        Precipitation_Array = convert(Matrix, Precipitation)
                        startindex = findfirst(isequal("01.01."*string(startyear)*" 07:00:00   "), Precipitation_Array)
                        endindex = findfirst(isequal("31.12."*string(endyear)*" 07:00:00   "), Precipitation_Array)
                        Precipitation_Array = Precipitation_Array[startindex[1]:endindex[1],:]
                        Precipitation_Array[:,1] = Date.(Precipitation_Array[:,1], Dates.DateFormat("d.m.y H:M:S   "))
                        # find duplicates and remove them
                        df = DataFrame(Precipitation_Array)
                        df = unique!(df)
                        # drop missing values
                        df = dropmissing(df)
                        Precipitation_Array = convert(Matrix, df)
                        Elevation_HRUs, Precipitation, Nr_Elevationbands = getprecipitationatelevation(Elevations_All_Zones[i], Precipitation_Gradient, Precipitation_Array[:,2])
                        push!(Precipitation_All_Zones, Precipitation)
                        push!(Nr_Elevationbands_All_Zones, Nr_Elevationbands)
                        push!(Elevations_Each_Precipitation_Zone, Elevation_HRUs)
                elseif ID_Prec_Zones[i] == 17700
                        Precipitation_Array = Precipitation_17700
                        # for all non data values use values of other precipitation zone
                        Elevation_HRUs, Precipitation, Nr_Elevationbands = getprecipitationatelevation(Elevations_All_Zones[i], Precipitation_Gradient, Precipitation_Array)
                        push!(Precipitation_All_Zones, Precipitation)
                        push!(Nr_Elevationbands_All_Zones, Nr_Elevationbands)
                        push!(Elevations_Each_Precipitation_Zone, Elevation_HRUs)
                end

                #glacier area only for 17700, for 114926 file contains only zeros
                Glacier_Area = CSV.read(local_path*"HBVModel/Defreggental/Glaciers_Elevations_"*string(ID_Prec_Zones[i])*"_evolution_69_06.csv",  header= true, delim=',')
                Years = collect(startyear:endyear)
                glacier_daily = zeros(Total_Elevationbands_Catchment)
                for current_year in Years
                        glacier_current_year = Glacier_Area[!, string(current_year)]
                        current_glacier_daily = repeat(glacier_current_year, 1, Dates.daysinyear(current_year))
                        glacier_daily = hcat(glacier_daily, current_glacier_daily)
                end
                push!(Glacier_All_Zones, glacier_daily[:,2:end])

                index_HRU = (findall(x -> x==ID_Prec_Zones[i], Areas_HRUs[1,2:end]))
                # for each precipitation zone get the relevant areal extentd
                Current_Areas_HRUs = convert(Matrix, Areas_HRUs[2: end, index_HRU])
                # the elevations of each HRU have to be known in order to get the right temperature data for each elevation
                Area_Bare_Elevations, Bare_Elevation_Count = getelevationsperHRU(Current_Areas_HRUs[:,1], Elevation_Catchment, Elevation_HRUs)
                Area_Forest_Elevations, Forest_Elevation_Count = getelevationsperHRU(Current_Areas_HRUs[:,2], Elevation_Catchment, Elevation_HRUs)
                Area_Grass_Elevations, Grass_Elevation_Count = getelevationsperHRU(Current_Areas_HRUs[:,3], Elevation_Catchment, Elevation_HRUs)
                Area_Rip_Elevations, Rip_Elevation_Count = getelevationsperHRU(Current_Areas_HRUs[:,4], Elevation_Catchment, Elevation_HRUs)
                #print(Bare_Elevation_Count, Forest_Elevation_Count, Grass_Elevation_Count, Rip_Elevation_Count)
                @assert 1 - eps(Float64) <= sum(Area_Bare_Elevations) <= 1 + eps(Float64)
                @assert 1 - eps(Float64) <= sum(Area_Forest_Elevations) <= 1 + eps(Float64)
                @assert 1 - eps(Float64) <= sum(Area_Grass_Elevations) <= 1 + eps(Float64)
                @assert 1 - eps(Float64) <= sum(Area_Rip_Elevations) <= 1 + eps(Float64)

                Area = Area_Zones[i]
                Current_Percentage_HRU = Percentage_HRU[:,1 + i]/Area
                # calculate percentage of elevations
                Perc_Elevation = zeros(Total_Elevationbands_Catchment)
                for j in 1 : Total_Elevationbands_Catchment
                        for h in 1:4
                                Perc_Elevation[j] += Current_Areas_HRUs[j,h] * Current_Percentage_HRU[h]
                        end
                end
                Perc_Elevation = Perc_Elevation[(findall(x -> x!= 0, Perc_Elevation))]
                @assert 0.99 <= sum(Perc_Elevation) <= 1.01
                push!(Elevation_Percentage, Perc_Elevation)
                # calculate the inputs once for every precipitation zone because they will stay the same during the Monte Carlo Sampling
                bare_input = HRU_Input(Area_Bare_Elevations, Current_Percentage_HRU[1],zeros(length(Bare_Elevation_Count)) , Bare_Elevation_Count, length(Bare_Elevation_Count), (Elevations_All_Zones[i].Min_elevation + 100, Elevations_All_Zones[i].Max_elevation - 100), (Snow_Threshold, Height_Threshold), 0, [0], 0, [0], 0, 0)
                forest_input = HRU_Input(Area_Forest_Elevations, Current_Percentage_HRU[2], zeros(length(Forest_Elevation_Count)) , Forest_Elevation_Count, length(Forest_Elevation_Count), (Elevations_All_Zones[i].Min_elevation + 100, Elevations_All_Zones[i].Max_elevation - 100), (Snow_Threshold, Height_Threshold), 0, [0], 0, [0],  0, 0)
                grass_input = HRU_Input(Area_Grass_Elevations, Current_Percentage_HRU[3], zeros(length(Grass_Elevation_Count)) , Grass_Elevation_Count,length(Grass_Elevation_Count), (Elevations_All_Zones[i].Min_elevation + 100, Elevations_All_Zones[i].Max_elevation - 100), (Snow_Threshold, Height_Threshold), 0, [0], 0, [0],  0, 0)
                rip_input = HRU_Input(Area_Rip_Elevations, Current_Percentage_HRU[4], zeros(length(Rip_Elevation_Count)) , Rip_Elevation_Count, length(Rip_Elevation_Count), (Elevations_All_Zones[i].Min_elevation + 100, Elevations_All_Zones[i].Max_elevation - 100),(Snow_Threshold, Height_Threshold), 0, [0], 0, [0],  0, 0)
                all_inputs = [bare_input, forest_input, grass_input, rip_input]
                #print(typeof(all_inputs))
                push!(Inputs_All_Zones, all_inputs)

                bare_storage = Storages(0, zeros(length(Bare_Elevation_Count)), zeros(length(Bare_Elevation_Count)), zeros(length(Bare_Elevation_Count)), 0)
                forest_storage = Storages(0, zeros(length(Forest_Elevation_Count)), zeros(length(Forest_Elevation_Count)), zeros(length(Bare_Elevation_Count)), 0)
                grass_storage = Storages(0, zeros(length(Grass_Elevation_Count)), zeros(length(Grass_Elevation_Count)), zeros(length(Bare_Elevation_Count)), 0)
                rip_storage = Storages(0, zeros(length(Rip_Elevation_Count)), zeros(length(Rip_Elevation_Count)), zeros(length(Bare_Elevation_Count)), 0)

                all_storages = [bare_storage, forest_storage, grass_storage, rip_storage]
                push!(Storages_All_Zones, all_storages)
        end
        # ---------------- CALCULATE OBSERVED OBJECTIVE FUNCTIONS -------------------------------------
        # calculate the sum of precipitation of all precipitation zones to calculate objective functions
        Total_Precipitation = Precipitation_All_Zones[1][:,1]*Area_Zones_Percent[1] + Precipitation_All_Zones[2][:,1]*Area_Zones_Percent[2]
        # end of spin up time is 3 years after the start of the calibration and start in the month October
        index_spinup = findfirst(x -> Dates.year(x) == firstyear + 2 && Dates.month(x) == 10, Timeseries)
        # evaluations chouls alsways contain whole year
        index_lastdate = findfirst(x -> Dates.year(x) == lastyear && Dates.month(x) == 10, Timeseries) - 1

        delete_days = readdlm(local_path*"HBVModel/Defreggental/Delete_Days.csv", ',', Int)
        Timeseries_Obj = Timeseries[index_spinup: index_lastdate]
        deleteat!(Timeseries_Obj, delete_days)
        Observed_Discharge_Obj = Observed_Discharge[index_spinup: index_lastdate] .* scale_factor_Discharge
        observed_AC_1day = autocorrelation(Observed_Discharge_Obj, 1)
        observed_AC_90day = autocorrelationcurve(Observed_Discharge_Obj, 90)[1]

        deleteat!(Observed_Discharge_Obj, delete_days)
        Total_Precipitation_Obj = Total_Precipitation[index_spinup: index_lastdate]
        deleteat!(Total_Precipitation_Obj, delete_days)
        #calculating the observed FDC; AC; Runoff
        observed_FDC = flowdurationcurve(log.(Observed_Discharge_Obj))[1]
        # observed_AC_1day = autocorrelation(Observed_Discharge_Obj, 1)
        # observed_AC_90day = autocorrelationcurve(Observed_Discharge_Obj, 90)[1]
        observed_monthly_runoff = monthlyrunoff(Area_Catchment, Total_Precipitation_Obj, Observed_Discharge_Obj, Timeseries_Obj)[1]
        # ---------------- START MONTE CARLO SAMPLING ------------------------
        #All_Goodness_new = []
        All_Goodness = zeros(29)
        #All_Parameter_Sets = Array{Any, 1}[]

        GWStorage = 55.0
        print("worker ", ID, " preparation finished", "\n")
        count = 1
        number_Files = 0
        #best_calibrations = readdlm("/home/sarah/Master/Thesis/Calibrations/Defreggental_less_dates/Defreggental_Parameterfit_All_runs_less_dates_best_500010.csv", ',')
        # parameters_best_calibrations = nmax[1+71430*(ID-1):71430*ID,:]
        # print("startvalue ", 1+71430*(ID-1), "endvalue", 71430*ID)

        #All_discharge = Array{Any, 1}[]
        for n in 1 : 1:nmax#size(parameters_best_calibrations)[1]
                Current_Inputs_All_Zones = deepcopy(Inputs_All_Zones)
                Current_Storages_All_Zones = deepcopy(Storages_All_Zones)
                Current_GWStorage = deepcopy(GWStorage)
                parameters, slow_parameters, parameters_array = parameter_selection_defreggental()

                #beta_Bare, beta_Forest, beta_Grass, beta_Rip, Ce, Interceptioncapacity_Forest, Interceptioncapacity_Grass, Interceptioncapacity_Rip, Kf_Rip, Kf, Ks, Meltfactor, Mm, Ratio_Pref, Ratio_Riparian, Soilstoaragecapacity_Bare, Soilstoaragecapacity_Forest, Soilstoaragecapacity_Grass, Soilstoaragecapacity_Rip, Temp_Thresh = parameters_best_calibrations[n, :]
                # bare_parameters = Parameters(beta_Bare, Ce, 0, 0.0, Kf, Meltfactor, Mm, Ratio_Pref, Soilstoaragecapacity_Bare, Temp_Thresh)
                # forest_parameters = Parameters(beta_Forest, Ce, 0, Interceptioncapacity_Forest, Kf, Meltfactor, Mm, Ratio_Pref, Soilstoaragecapacity_Forest, Temp_Thresh)
                # grass_parameters = Parameters(beta_Grass, Ce, 0, Interceptioncapacity_Grass, Kf, Meltfactor, Mm, Ratio_Pref, Soilstoaragecapacity_Grass, Temp_Thresh)
                # rip_parameters = Parameters(beta_Rip, Ce, 0.0, Interceptioncapacity_Rip, Kf, Meltfactor, Mm, Ratio_Pref, Soilstoaragecapacity_Rip, Temp_Thresh)
                # slow_parameters = Slow_Paramters(Ks, Ratio_Riparian)
                #
                # parameters = [bare_parameters, forest_parameters, grass_parameters, rip_parameters]
                # parameters_array = parameters_best_calibrations[n, :]

                Discharge, Snow_Extend = runmodelprecipitationzones_glacier(Potential_Evaporation, Glacier_All_Zones, Precipitation_All_Zones, Temperature_Elevation_Catchment, Current_Inputs_All_Zones, Current_Storages_All_Zones, Current_GWStorage, parameters, slow_parameters, Area_Zones, Area_Zones_Percent, Elevation_Percentage, Elevation_Zone_Catchment, ID_Prec_Zones, Nr_Elevationbands_All_Zones, observed_snow_cover, start2000)
                Discharge = Discharge * 1000 / Area_Catchment * (3600 * 24)
                # don't calculate the goodness of fit for the spinup time!
                Discharge_Obj = Discharge[index_spinup:index_lastdate]
                deleteat!(Discharge_Obj, delete_days)
                Goodness_Fit, ObjFunctions = objectivefunctions_delete_days(Discharge[index_spinup:index_lastdate], Discharge_Obj, Snow_Extend, Observed_Discharge_Obj, observed_FDC, observed_AC_1day, observed_AC_90day, observed_monthly_runoff, Area_Catchment, Total_Precipitation_Obj, Timeseries_Obj)
                #if goodness higher than -9999 save it
                #if goodness higher than -9999 save it
                if Goodness_Fit != -9999
                        Goodness = [Goodness_Fit, ObjFunctions, parameters_array]
                        Goodness = collect(Iterators.flatten(Goodness))
                        All_Goodness = hcat(All_Goodness, Goodness)
                        #append!(All_Goodness_new, Goodness_new)
                        #push!(All_Goodness, Goodness)
                        if size(All_Goodness)[2]-1 == 100
                                All_Goodness = transpose(All_Goodness[:, 2:end])
                                if count != 100
                                        open(local_path*"HBVModel/Defreggental_Parameterfit_less_dates_snow_redistr_"*string(ID)*"_"*string(number_Files)*".csv", "a") do io
                                                writedlm(io, All_Goodness,",")
                                        end
                                        count+= 1
                                else
                                        open(local_path*"HBVModel/Defreggental_Parameterfit_less_dates_snow_redistr_"*string(ID)*"_"*string(number_Files)*".csv", "a") do io
                                                writedlm(io, All_Goodness,",")
                                        end
                                        count = 1
                                        number_Files += 1
                                end

                                #print("worker ", ID, " wrote 100 tested parameter sets to file.", "\n")
                                All_Goodness = zeros(29)
                        end
                end
                if mod(n, 1000) == 0
                        print("number of runs", n, "\n")
                end
        end
        All_Goodness = transpose(All_Goodness[:, 2:end])
        open(local_path*"HBVModel/Defreggental_Parameterfit_less_dates_snow_redistr_"*string(ID)*".csv", "a") do io
                writedlm(io, All_Goodness,",")
        end
end
#nmax = readdlm("/home/sarah/Master/Thesis/Calibrations/Defreggental_less_dates/Defreggental_Parameterfit_All_runs_less_dates_best_500010.csv", ',')[:,10:29]
@time begin
#run_MC(1,100)
nmax = 10
pmap(ID -> run_MC(ID, nmax) , [1])
end


# load the include -
include("Include.jl")

# how many samples?
number_of_samples = 500
number_of_parameters = 12
ensemble_archive = zeros(number_of_parameters+1,1); # first row is the fitness

# load the training data -
path_to_training_data = joinpath(_PATH_TO_DATA, "Training-Synthetic-Thrombin-TF-1K.csv")
training_df = CSV.read(path_to_training_data, DataFrame)

# build the model structure -
path_to_model_file = joinpath(_PATH_TO_MODEL, "Coagulation.net")
model_buffer = read_model_file(path_to_model_file)

# build the default model structure -
model = build_default_model_dictionary(model_buffer)

# main loop -
p_previous = nothing
for i ∈ 1:number_of_samples

    # run the learn routine -
    (p, T, Xₘ, Yₘ, Y) = learn_optim(i, model, training_df; pₒ = nothing)

    # compute the fitness -
    fitness = norm((Yₘ .- Y).^2)
    global ensemble_archive[1] = fitness

    # cache the parameters -
    for k ∈ 1:number_of_parameters
        global ensemble_archive[k+1] = p[k]
    end

    # dump parameters to disk (just in case) -
    path_to_synthetic_ensemble = joinpath(_PATH_TO_TMP, "PSET-Synthetic-1k-P$(i).csv")
    CSV.write(path_to_synthetic_ensemble, Tables.table(ensemble_archive); header=["parameters"])

    # dump output to disk -
    data_output = [Y Yₘ]
    data_output_header = ["actual", "simulated"]
    path_to_synthetic_patient_otuput = joinpath(_PATH_TO_TMP, "OUT-Synthetic-1k-P$(i).csv")
    CSV.write(path_to_synthetic_patient_otuput, Tables.table(data_output); header = data_output_header)

    # dump state to disk -
    data_state = transpose(vcat(reshape(T,1,length(T)), Xₘ))
    data_state_header = ["T", "FII", "FVII", "FV", "FX", "FVIII", "FIX", "FXI", "FXII", "FIIa", "FVIIa", "FVa", "FXa", "FVIIIa", "FIXa", "FXIa", "FXIIa", "FIIa_inactive"]
    path_to_synthetic_patient_simulation = joinpath(_PATH_TO_TMP, "SIM-Synthetic-1k-P$(i).csv")
    CSV.write(path_to_synthetic_patient_simulation, Tables.table(data_state); header=data_state_header)

    # update/clean up for the next patient -
    global p_previous = p
end


#### Main file ####
# Use this file to run the scripts all at once with conditional arguments
# Useful to update results & figures
# BUT be careful as some steps might take a while

# Load required packages & scripts
include("required.jl")

## Part I - Data preparation ####

# Set conditional arguments used in scripts
save_prepdata = true # should preparation data be overwritten (optional)
save_figures = true # should figures be overwritten (optional)

# 0a. EBD data extraction (if needed) (possibly very long)
@info "Running 00a_ebd_extraction.R"
@time run("Rscript src/00a_ebd_extraction.R")

# 0b. EBD data preparation (if needed)
@info "Running 00b_ebd_preparation.jl"
@time include("00b_ebd_preparation.jl")

# 0c. Landcover data preparation (if needed)
@info "Running 00c_landcover.jl"
@time include("00c_landcover.jl")

## Part II - Distributions & modelling ####

# Set conditional arguments
outcome = "raw" # desired outcome (required)
create_distributions = true # should distributions be computed (optional, loaded otherwise)
save_data = true # should data files be overwritten (optional)
save_figures = true # should figures be overwritten (optional)

# 1. Generate presence-absence data & single species map
@info "Running 01_distributions.jl"
@time include("01_distributions.jl")

# 2. Train BART models
@info "Running 02_training_bart.R"
@time run("Rscript src/02_training_bart.R")

# 3. Get BART predictions
@info "Running 03_predictions_bart.jl"
@time include("03_predictions_bart.jl")

## Part III - Main analyses ####

# Set conditional arguments
outcome = "raw" # desired outcome (required), choose either raw or bart
outcome = "bart"
save_figures = true # should figures be overwritten (optional)

# 4. Run main analyses (Y-matrix, richness, LCBD, relationship)
@info "Running 04_full-extent.jl"
@time include("04_full-extent.jl")

# 5. Run subareas analyses
@info "Running 05_subareas.jl"
@time include("05_subareas.jl")

## Part IV - Additional analyses ####

# These analyses will re-run the previous scripts.
# Better to reset the conditional arguments to avoid overwriting things
outcome = nothing
save_figures = nothing
save_data = nothing

# Set conditional arguments for additional analyses scripts
save_additional_figures = true
save_additional_data = true

# 6. Run rare species analyses
# (This script needs one of the two outcomes)
outcome = "raw"
outcome = "bart"
@info "Running 06_rare-species.jl"
@time include("06_rare-species.jl")

# Better to reset the outcome after this script
outcome = nothing

# 7-8-9. Run comparison analyses (between raw data & predictions)
# (These scripts don't need to set the outcome)

# 7. Prepare data
@info "Running 07_comparison_data.jl"
@time include("07_comparison_data.jl")

# 8. Run GLMs
@info "Running 08_comparison_glm.R"
@time run(`Rscript src/08_comparison_glm.R`) # this script might overflow memory

# 9. Compare results
@info "Running 09_comparison_plots.jl"
@time include("09_comparison_plots.jl")

# Better to reset the outcome after these scripts
outcome = nothing


import FRS_Household_Getter: initialise, get_household
import Test: @testset, @test
import Model_Household: Household, Person, People_Dict, BUAllocation,
      PeopleArray, printpids,
      BenefitUnit, BenefitUnits, default_bu_allocation,
      get_benefit_units, get_head, get_spouse, num_people

start_year=2015

@testset "benefit unit allocations" begin
      rc = @timed begin
         num_households,total_num_people,nhh2 = initialise(
            household_name = "model_households_scotland",
            people_name    = "model_people_scotland",
            start_year = start_year )
      end
      println( "num_households=$num_households, num_people=$num_people")
      people_count = 0
      for hhno in 1:num_households
            hh = get_household( hhno )
            # println("people in HH")
            # printpids(hh.people)

            hhsize = size(collect(keys(hh.people)))[1]
            people_count += hhsize
            bus = get_benefit_units( hh )
            buallocation = default_bu_allocation( hh )
            nbus = size( bus )[1]
            @test 0 < nbus < 10
            by_bu_people_count = 0
            i = 0
            for bu in bus
                  # println( "from BU")
                  # printpids(bu)

                  i += 1
                  bu_people_count = 1
                  head = get_head( bu )
                  @test head.age >= 16
                  spouse = get_spouse( bu )
                  if spouse != nothing
                        bu_people_count += 1
                        @test spouse.age >= 16
                  end
                  for chno in bu.children
                        child = bu.people[chno]
                        @test child.age <= 19
                        bu_people_count += 1
                  end
                  by_bu_people_count += bu_people_count
                  @test bu_people_count == num_people( bu )
                  @test bu_people_count == size( buallocation[i])[1]
            end
            @test by_bu_people_count == hhsize

            bua_people_count = 0
            for bua in buallocation
                  bua_people_count += size( bua )[1]
            end
            # println("BU Allocation")
            # printpids(buallocation)
            @test bua_people_count == hhsize
      end
      @test people_count == total_num_people
end