### Persefone.jl - a model of agricultural landscapes and ecosystems in Europe.
###
### This file contains structs and functions for implementing Dynamic Energy Budgets.
###

## XXX THE CODE IN THIS FILE IS CURRENTLY NOT USED AND LIKELY WON'T BE IN FUTURE.
## HOWEVER, I PREFER TO KEEP IT FOR NOW IN CASE WE EVER DO. (DV, 29th July 2024)

#TODO add units

## STRUCTS

"""
    DEBparameters

An immutable struct to save the parameter list for a species' Dynamic Energy Budget
model. (See Sousa et al., 2010.)
"""
struct DEBparameters
    maxsearchrate::Float64 # maximum amount of food handled per day (F_m)
    assimilation::Float64 # percentage of food biomass assimilated (y_EX)
    growthefficiency::Float64 # yield of structure on reserve (y_VE)
    v::Float64 # energy conductance
    surfacesomaticmaintenance::Float64 # surface-specific somatic maintenance (J_ET)
    volumesomaticmaintenance::Float64 # volume-specific somatic maintenance (J_EM)
    #maturitymaintenance::Float64 # specific maturity maintenance (k_J)
    kappa::Float64 # percentage allocated to somatic maintenance and growth (k)
    reproductionefficiency::Float64 # overhead cost of reproduction (k_R)
    eggsize::Float64 # weight of an egg/embryo, = initial amount of reserve (M_E0)
    juvenilesize::Float64 # structure size at which an embryo becomes a juvenile (M_Hb)
    adultsize::Float64 # structure size at which a juvenile becomes an adult (M_Hp)
end

"""
    EnergyBudget

This struct represents an individual's energy balance, as conceptualised by the
Dynamic Energy Budget theory. Upon assimilation, energy is first stored as biomass
in a reserve buffer, before being used for maintenance, growth, and reproduction.
(Note that this is a simplified model form which ignores maturity as a separate buffer.)

**Sources:**

- Malishev & Kramer-Schadt (2021). Movement, models, and metabolism: Individual-based energy budget models as next-generation extensions for predicting animal movement outcomes across scales. Ecological Modelling, 441, 109413. https://doi.org/10.1016/j.ecolmodel.2020.109413
- Marques et al. (2018). The AmP project: Comparing species on the basis of dynamic energy budget parameters. PLOS Computational Biology,14(5), e1006100. https://doi.org/10.1371/journal.pcbi.1006100
- Sibly et al. (2013). Representing the acquisition and use of energy by individuals in agent-based models of animal populations. Methods in Ecology and Evolution, 4(2), 151–161. https://doi.org/10.1111/2041-210x.12002
- Sousa et al. (2010). Dynamic energy budget theory restores coherence in biology. Philosophical Transactions of the Royal Society B: Biological Sciences, 365(1557), 3413–3428. https://doi.org/10.1098/rstb.2010.0166
- Kooijman, S. A. L. M. (2009). Dynamic energy and mass budgets in biological systems (3rd ed). Cambridge University Press. https://www.researchgate.net/profile/Edgar-Meza-3/post/Is_there_a_toxicokinetic_model_for_daphnia_magna_or_other_zooplankton/attachment/59d62cf579197b807798b396/AS%3A348547653357569%401460111644286/download/Dynamic+Energy+Budget+theory+-+Kooijman.pdf
- *compare with:* Brown et al. (2004). Toward a metabolic theory of ecology. Ecology, 85(7), 1771–1789. https://doi.org/10.1890/03-9000
"""
mutable struct EnergyBudget
    params::DEBparameters
    reserve::Float64    # M_E
    structure::Float64  # M_V
    #maturity::Float64  # M_H
    offspring::Float64  # M_ER
end


## INTERNAL CALCULATIONS

"""
    volumetriclength(energybudget)

Calculate the structural length in cm based on an individual's weight
(assuming a density of 1 g/cm³ to calculate volume, see Kooijman 2009).
"""
function volumetriclength(deb::EnergyBudget)
    #TODO is cm the right unit?
    weight = deb.reserve + deb.structure + deb.offspring
    vlength = weight ^ (1/3)
    vlength
end

"""
    maturitymaintenance(energybudget)

Calculate the specific maturity maintenance k_J.
(Internal function.)
"""
function maturitymaintenance(deb::EnergyBudget)
    y_VE = deb.params.growthefficiency
    J_EM = deb.params.volumesomaticmaintenance
    M_V = deb.structure
    k_J = (y_VE * J_EM) / M_V
    k_J
end

"""
    scaledreservedensity(energybudget)

Calculate the scaled reserve density e.
(Internal function.)
"""
function scaledreservedensity(deb::EnergyBudget)
    J_EAm = deb.params.assimilation * deb.params.maxsearchrate
    m_E = deb.reserve / deb.structure
    m_Em = J_EAm / (deb.params.v * deb.structure)
    e = m_E / m_Em
    e
end

"""
    investmentratio(energybudget)

Calculate the investment ratio g.
(Internal function.)
"""
function investmentratio(deb::EnergyBudget)
    J_EAm = deb.params.assimilation * deb.params.maxsearchrate
    y_VE = deb.params.growthefficiency
    k = deb.params.kappa
    v = deb.params.v
    M_V = deb.structure
    g = (v * M_V) / (k * J_EAm * y_VE)
    g
end

"""
    growthrate(energybudget)

Calculate the specific growth rate r.
(Internal function.)
"""
function growthrate(deb::EnergyBudget)
    J_EAm = deb.params.assimilation * deb.params.maxsearchrate
    J_ET = deb.params.surfacesomaticmaintenance
    J_EM = deb.params.volumesomaticmaintenance
    k = deb.params.kappa
    v = deb.params.v
    e = scaledreservedensity(deb)
    g = investmentratio(deb)
    L = volumetriclength(deb)
    L_m = k * (J_EAm / J_EM) # max length
    L_T = J_ET / J_EM # heating length
    r = v * (e/L - (1+L_T/L)/L_m) / (e+g)
    r
end

"""
    mobilisation(energybudget)

Calculate the mobilisation rate J_EC.
(Internal function.)
"""
function mobilisation(deb::EnergyBudget)
    M_E = deb.reserve
    r = growthrate(deb)
    v = deb.params.v
    L = volumetriclength(deb)
    J_EC = M_E * (v/L - r)
    J_EC
end


## PUBLIC INTERFACE

"""
    update!(energybudget)

Carry out a daily update of the energy budget. Mobilises reserves and allocates
these to maintenance (prioritised), growth, and reproduction.

Return `true` if the individual has enough energy to survive, or `false` if the
reserve is empty and it starves.
"""
function update!(deb::EnergyBudget)
    J_ET = deb.params.surfacesomaticmaintenance
    J_EM = deb.params.volumesomaticmaintenance
    k_R = deb.params.reproductionefficiency
    y_VE = deb.params.growthefficiency
    #k_J = deb.params.maturitymaintenance
    k_J = maturitymaintenance(deb)
    L = volumetriclength(deb)
    k = deb.params.kappa
    M_V = deb.structure
    #M_H = deb.maturity
    M_E = deb.reserve

    J_EC = mobilisation(deb)

    ## Somatic maintenance and growth
    J_ES = J_EM * (L^3) + J_ET * (L^2) # somatic maintenance
    J_EG = J_EC * k - J_ES # energy devoted to growth
    J_VG = J_EG * y_VE # actual growth
    #J_VG = growthrate(deb) * deb.structure # r*M_V
    #XXX check if J_VG == J_EG * y_VE == r * M_V
    deb.structure += J_VG

    # juveniles grow until they reach adult size
    #XXX growth should stop automatically at M_V == M_Hp
    if deb.structure < adultsize
        growth = (mobilisation*deb.params.kappa) - somaticmaintenance
        deb.structure += growth
    end

    # juveniles increase in maturity, adults invest into reproduction
    J_EJ = k_J * M_H # maturity maintenance
    J_ER = J_EC*(1-k) - J_EJ # resources for reproduction
    if M_V >= deb.params.adultsize
        deb.offspring += J_ER * k_R
    else
        #deb.maturity += J_ER #XXX I'm ignoring the maturity buffer for now
    end

    #XXX Check that J_EC == J_ES + J_EG + J_EJ + J_ER
    deb.reserve -= J_EC
    return deb.reserve > 0
end

"""
    feed!(quantity, energybudget)

Consume a given quantity of food. Expands the energy reserve by an
amount determined by the assimilation rate. Returns `true` if successful,
`false` if the reserve is already full.
"""
function feed!(quantity::Float64, deb::EnergyBudget)
    F_m = deb.params.maxsearchrate
    y_EX = deb.params.assimilation
    M_V = deb.structure
    M_E = deb.reserve
    v = deb.params.v

    J_EAm = y_EX * F_m # max assimilation rate
    m_E = M_E / M_V # reserve density
    m_Em = J_EAm / (v * M_V) # max reserve density

    # only feed if the reserve is not yet at maximum capacity
    if m_E < m_Em
        deb.reserve += quantity * y_EX
        return true
    else
        return false
    end
end

"""
    reproduce!(energybudget)

If there is sufficient energy in the `offspring` buffer of an adult, produce an
embryo/egg, reducing the parent energy in the process. Returns the embryo's energy
budget, or `nothing` if the conditions are not met.
"""
function reproduce!(deb::EnergyBudget)
    M_V = deb.structure
    M_ER = deb.offspring
    M_Hp = deb.params.adultsize
    M_E0 = deb.params.eggsize
    
    if M_V >= M_Hp && M_ER >= M_E0
        deb.offspring -= M_E0
        return EnergyBudget(deb.params, M_E0, 0.0, 0.0)
    else
        return nothing
    end
end