### Persefone.jl - a model of agricultural landscapes and ecosystems in Europe.
###
### This file is responsible for managing the animal modules and contains functions
### and types integrating the nature submodel with the rest of Persefone.
###


## An enum used to assign a sex to each animal
@enum Sex hermaphrodite male female
#XXX hermaphrodite probably is not needed and its implementation is iffy

"""
    Animal

This is the generic agent type for all animals. Individual species are created
using the [`@species`](@ref) macro. All species contain the following fields:

- `id` An integer unique identifier for this individual.
- `sex` male, female, or hermaphrodite.
- `parents` The IDs of the individual's parents.
- `pos` An (x, y) coordinate tuple.
- `age` The age of the individual in days.
- `phase` The update function to be called during the individual's current life phase.
- `energy` A [DEBparameters](@ref) struct for calculating energy budgets.
- `offspring` A vector containing the IDs of an individual's children.
"""
abstract type Animal <: ModelAgent end

"""
    speciesof(animal)

Return the species name of this animal as a string.
"""
function speciesof(a::Animal)
    string(typeof(a))
end
    
"""
    animalid(animal)

A small utility function to return a string with the species name and ID of an animal.
"""
function animalid(a::Animal)
    return "$(speciesof(a)) $(a.id)"
end

"""
    stepagent!(animal, model)

Update an animal by one day, executing it's currently active phase function.
"""
function stepagent!(animal::Animal, model::SimulationModel)
    animal.age += 1
    animal.phase(animal, model)
end

"""
    initnature!(model)

Initialise the model with all simulated animal populations.
"""
function initnature!(model::SimulationModel)
    # The config file determines which species are simulated in this run
    for speciesname in @param(nature.targetspecies)
        # Call each species' initialisation function
        @eval $(Symbol(speciesname))().initialise!($(Symbol(speciesname)), model)
    end
    # Initialise the data output
    initecologicaldata(model)
end

"""
    updatenature!(model)

Run processes that affect all animals.
"""
function updatenature!(model::SimulationModel)
    # Update all animals. Dead animals are replaced by `nothing`, we can skip these.
    #XXX I assume that keeping all those `nothing`s in the animal vector won't lead
    # to memory bloat, but I will have to check that.
    for a in model.animals
        !isnothing(a) && stepagent!(a, model)
    end
    # The migrant pool is sorted by date of return, so we can simply look at the top
    # of the stack to check whether any animals are returning today.
    while !isempty(model.migrants) && model.migrants[1].second <= model.date
        returnee = model.migrants[1].first
        model.animals[returnee.id] = returnee
        @debug "$(animalid(returnee)) has returned."
        deleteat!(model.migrants, 1)
    end
end