### Persefone.jl - a model of agricultural landscapes and ecosystems in Europe.
###
### This file contains code for the fields that farmers manage.
###

"""
    FarmPlot

A struct representing a single field, on which a crop can be grown.
"""
mutable struct FarmPlot <: ModelAgent
    const id::Int64
    pixels::Vector{Tuple{Int64, Int64}}
    farmer::Int64
    soiltype::SoilType
    cropstate::AbstractCropState
end

croptype(f::FarmPlot) = croptype(f.cropstate)
cropname(f::FarmPlot) = cropname(croptype(f))
cropheight(f::FarmPlot) = cropheight(f.cropstate)
cropcover(f::FarmPlot) = cropcover(f.cropstate)
cropyield(f::FarmPlot) = cropyield(f.cropstate)
isharvestable(f::FarmPlot) = isharvestable(f.cropstate)

function setsoiltype!(f::FarmPlot, soiltype::SoilType)
    f.soiltype = soiltype
    setsoiltype!(f.cropstate, soiltype)
    return nothing
end

"""
    stepagent!(farmplot, model)

Update a farm plot by one day.
"""
function stepagent!(farmplot::FarmPlot, model::SimulationModel)
    stepagent!(farmplot.cropstate, model)
end

"""
    sow!(farmplot, model, cropname)

Sow the specified crop on the farmplot.
"""
function sow!(farmplot::FarmPlot, model::SimulationModel, cropname::String)
    new_croptype = model.crops[cropname]
    createevent!(model, farmplot.pixels, sowing)
    if typeof(new_croptype) === typeof(croptype(farmplot))
        sow!(farmplot.cropstate, model, cropname)
    else
        farmplot.cropstate = makecropstate(new_croptype, model, farmplot.soiltype)
    end
    @debug "Farmer $(farmplot.farmer) sowed $(cropname) on farmplot $(farmplot.id)."
end

"""
    harvest!(farmplot, model)

Harvest the crop of this farmplot.
"""
function harvest!(farmplot::FarmPlot, model::SimulationModel)
    createevent!(model, farmplot.pixels, harvesting)
    harvest!(farmplot.cropstate, model)  # TODO: multiply with area to return units of `g`
    @debug "Farmer $(farmplot.farmer) harvested $(cropname(farmplot)) from farmplot $(farmplot.id)."
end

"""
    @sow(cropname)

Sow the named crop on the current field. Requires the variables `field` and `model`.
"""
macro sow(cropname)
    :(sow!($(esc(:field)), $(esc(:model)), $(esc(cropname))))
end

"""
    @harvest()

Harvest the current field. Requires the variables `field` and `model`.
"""
macro harvest()
    :(harvest!($(esc(:field)), $(esc(:model))))
end

## UTILITY FUNCTIONS

"""
    isgrassland(farmplot, model)

Classify a farmplot as grassland or not (i.e., is the landcover of >80% of its pixels grass?)
"""
function isgrassland(farmplot::FarmPlot, model::SimulationModel)
    proportiongrass = count(pos -> landcover(pos, model) == grass, farmplot.pixels) /
        length(farmplot.pixels)
    if proportiongrass > 0.8
        return true
    elseif proportiongrass > 0.2
        @debug "Unclear classification: farm plot $(farmplot.id) has $(proportiongrass*100)% grass."
        proportiongrass >= 0.5 && return true
    end
    return false
end

"""
    averagefieldsize(model)

Calculate the average field size in hectares for the model landscape.
"""
function averagefieldsize(model::SimulationModel)
    area_sum = sum(@areaof(length(fp.pixels)) for fp in model.farmplots; init=0.0u"m^2")
    area_avg = area_sum / length(model.farmplots)
    return area_avg |> ha
end

"""
    croptype(model, position)

Return the crop at this position, or nothing if there is no crop here (utility wrapper).
"""
function croptype(pos::Tuple{Int64,Int64}, model::SimulationModel)
    ismissing(model.landscape[pos...].fieldid) ? nothing :
              croptype(model.farmplots[model.landscape[pos...].fieldid])
end

"""
    cropname(model, position)

Return the name of the crop at this position, or an empty string if there is no crop here
(utility wrapper).
"""
function cropname(pos::Tuple{Int64,Int64}, model::SimulationModel)
    field = model.landscape[pos...].fieldid
    ismissing(field) ? "NA" : cropname(model.farmplots[field])
end

"""
    cropheight(model, position)

Return the height of the crop at this position, or nothing if there is no crop here
(utility wrapper).
"""
function cropheight(pos::Tuple{Int64,Int64}, model::SimulationModel)
    ismissing(model.landscape[pos...].fieldid) ? 0.0cm :  # TODO: 0.0cm correct here?
              cropheight(model.farmplots[model.landscape[pos...].fieldid])
end

"""
    cropcover(model, position)

Return the crop cover of the crop at this position, or nothing if
there is no crop here (utility wrapper).
"""
function cropcover(pos::Tuple{Int64,Int64}, model::SimulationModel)
    ismissing(model.landscape[pos...].fieldid) ? 0.0 :  # TODO: 0.0 correct here?
              cropcover(model.farmplots[model.landscape[pos...].fieldid])
end