plot_ensemble_mean <- function(recipe, fcst, mask = NULL, dots = NULL, outdir, output_conf) {
  ## TODO: Add 'anomaly' to plot title
  # Abort if frequency is daily
  if (recipe$Analysis$Variables$freq %in% c("daily", "daily_mean")) {
    stop("Visualization functions not yet implemented for daily data.")
  }
  latitude <- fcst$coords$lat
  longitude <- fcst$coords$lon
  archive <- get_archive(recipe)
  if (recipe$Analysis$Datasets$System$name == 'Multimodel'){
    system_name <- paste0('Multimodel-',
                          recipe$Analysis$Datasets$Multimodel$approach)
  } else {
    system_name <- archive$System[[recipe$Analysis$Datasets$System$name]]$name
  }
  start_date <- paste0(recipe$Analysis$Time$fcst_year,
                       recipe$Analysis$Time$sdate)
  if (tolower(recipe$Analysis$Horizon) == "seasonal") {
    init_month <- as.numeric(substr(recipe$Analysis$Time$sdate,
                                    start = 1, stop = 2))
  } else {
    ## TODO: Sort out decadal initial month (is it always January?)
    init_month <- 1
  }
  if (!is.null(recipe$Analysis$Workflow$Visualization$projection)) {
    projection <- tolower(recipe$Analysis$Workflow$Visualization$projection)
  } else {
    projection <- "cylindrical_equidistant"
  }
  # Compute ensemble mean
  ensemble_mean <- s2dv::MeanDims(fcst$data, 'ensemble')
  # Loop over variable dimension
  for (var in 1:fcst$dims[['var']]) {
    variable <- fcst$attrs$Variable$varName[[var]]
    var_long_name <- fcst$attrs$Variable$metadata[[variable]]$long_name
    units <- fcst$attrs$Variable$metadata[[variable]]$units
    # Subset ensemble mean by variable
    var_ens_mean <- ClimProjDiags::Subset(ensemble_mean,
                                          along = c("dat", "var",
                                                    "sday", "sweek"),
                                          indices = list(1, var, 1, 1),
                                          drop = 'selected')

    var_ens_mean <- Reorder(var_ens_mean, c("syear",
                                            "time",
                                            "longitude",
                                            "latitude"))
    ## TODO: Redefine column colors, possibly depending on variable
    if (variable == 'prlr') {
      palette = "BrBG"
      rev = F
    } else {
      palette = "RdBu"
      rev = T
    }
    # Define brks, centered around zero in the case of anomalies
    if (grepl("anomaly", var_long_name)) {
      variable <- paste(variable, "anomaly")
      max_value <- max(abs(var_ens_mean), na.rm = TRUE)
      ugly_intervals <- seq(-max_value, max_value, max_value/20)
      brks <- pretty(ugly_intervals, n = 12, min.n = 8)
      cols <- grDevices::hcl.colors(length(brks) - 1, palette, rev = rev)
    } else {
      if (variable == 'prlr') {
        cols <- c("#FFAB38", "white", "#41CBC9")
        col_fun <- colorRampPalette(cols)
      } else {
        cols <- c("#33BFD1", "white", "#FF764D")
        col_fun <- colorRampPalette(cols)
      }
      if (all(is.na(var_ens_mean))) {
        brks <- NULL
      } else {
        brks <- pretty(range(var_ens_mean, na.rm = T), n = 15, min.n = 8)
      }
      if (is.null(brks)) {
        cols <- NULL
      } else {
        cols <- col_fun(length(brks) - 1)
      }
    }

    for (i_syear in start_date) {
      if (length(start_date) == 1) {
        i_var_ens_mean <- ClimProjDiags::Subset(var_ens_mean,
                                                along = c("syear"),
                                                indices = which(start_date == i_syear),
                                                drop = 'selected')
        outfile <- paste0(outdir[[var]], "forecast_ensemble_mean-", start_date)
      } else {
        i_var_ens_mean <- ClimProjDiags::Subset(var_ens_mean,
                                                along = c("syear"),
                                                indices = which(start_date == i_syear),
                                                drop = 'selected')
        outfile <- paste0(outdir[[var]], "forecast_ensemble_mean-", i_syear)
      }
      # Mask
      if (!is.null(mask)) {
        outfile <- paste0(outfile, "_enscormask")
        var_mask <- ClimProjDiags::Subset(mask,
                                          along = c("var"),
                                          indices = var,
                                          drop = 'selected')
        dim_mask <- dim(var_mask)
        var_mask <- as.numeric(var_mask <= 0)
        dim(var_mask) <- dim_mask
      }
      # Dots
      if (!is.null(dots)) {
        outfile <- paste0(outfile, "_enscordots")
        var_dots <- ClimProjDiags::Subset(dots,
                                          along = c("var"),
                                          indices = var,
                                          drop = 'selected')
        dim_dots <- dim(var_dots)
        var_dots <- as.numeric(var_dots <= 0)
        dim(var_dots) <- dim_dots
      }
      toptitle <- paste0(system_name, " / ", str_to_title(var_long_name),
                         "\n", "Forecast Ensemble Mean / ", "Init.: ", i_syear)
      months <- lubridate::month(fcst$attrs$Dates[1, 1, which(start_date == i_syear), ],
                                 label = T, abb = F)
      years <- lubridate::year(fcst$attrs$Dates[1, 1, which(start_date == i_syear), ])

      if (recipe$Analysis$Workflow$Visualization$multi_panel) { 
        # Define name of output file and titles
        titles <- as.vector(months)
        # Plots
        PlotLayout(PlotEquiMap, c('longitude', 'latitude'),
                   i_var_ens_mean, longitude, latitude,
                   mask = mask,
                   dots = dots,
                   filled.continents = F,
                   toptitle = toptitle,
                   title_scale = 0.7,
                   subtitle_scale = 0.8,
                   subtitle_margin_scale = 2,
                   titles = titles,
                   units = units,
                   cols = cols,
                   brks = brks,
                   fileout = paste0(outfile, ".pdf"),
                   bar_label_digits = 4,
                   extra_margin = rep(1, 4),
                   bar_label_scale = 1.5,
                   axes_label_scale = 1.1)
      } else {
        # Define function and parameters depending on projection
        if (projection == 'cylindrical_equidistant') {
          fun <- PlotEquiMap
          output_configuration <- output_conf$PlotEquiMap$forecast_ensemble_mean
          base_args <- list(var = NULL, dots = NULL, mask = NULL,
                            lon = longitude, lat = latitude,
                            dot_symbol = 20, title_scale = 0.6,
                            font.main = 2,
                            filled.continents = F, brks = brks, cols = cols,
                            bar_label_digits = 4, bar_label_scale = 1.5,
                            axes_label_scale = 1, units = units)
          base_args[names(output_configuration)] <- output_configuration
        } else {
          fun <- PlotRobinson
          common_projections <- c("robinson", "stereographic", "lambert_europe")
          if (projection %in% common_projections) {
            target_proj <- get_proj_code(projection)
          } else {
            target_proj <- projection
          }
          base_args <- list(data = NULL, mask = NULL, dots = NULL,
                            lon = longitude, lat = latitude,
                            lon_dim = 'longitude', lat_dim = 'latitude',
                            target_proj = target_proj, legend = 's2dv',
                            style = 'point', brks = brks, cols = cols)
        }
        # Loop over forecast times
        for (i in 1:length(months)) {
          # Get forecast time label
          forecast_time <- match(months[i], month.name) - init_month + 1
          if (forecast_time < 1) {
            forecast_time <- forecast_time + 12
          }
          forecast_time <- sprintf("%02d", forecast_time)
          # Get mask subset
          if (!is.null(mask)) {
            mask_i <- Subset(var_mask, along = 'time', indices = i, drop = TRUE)
          } else {
            mask_i <- NULL
          }
          # Get dots subset
          if (!is.null(dots)) {
            dots_i <- Subset(var_dots, along = 'time', indices = i, drop = TRUE)
          } else {
            dots_i <- NULL
          }
          # Define plot title
          toptitle <- paste0(system_name, " / ",
                             str_to_title(var_long_name),
                             "\n", "Ensemble Mean / ",
                             months[i], " ", years[i],
                             " / Start date: ", 
                             format(as.Date(i_syear, format="%Y%m%d"), 
                                            "%d-%m-%Y"))
          # Define caption
          if (identical(fun, PlotRobinson)) {
            ## TODO: Customize technical details
            base_args[['caption']] <-
              paste0("Nominal start date: ", start_date, "\n",
                     "Forecast month: ", sprintf("%02d", i), "\n",
                     "Reference: ", recipe$Analysis$Datasets$Reference)
          }
          # Modify base arguments
          base_args[[1]] <- i_var_ens_mean[i, , ]
          fileout <- paste0(outfile, "_ft", sprintf("%02d", i), ".pdf")
          base_args$mask <- mask_i
          base_args$dots <- dots_i
          # Plot
          do.call(fun,
                  args = c(base_args,
                           list(toptitle = toptitle,
                                fileout = fileout)))
        }
      }
    }
  }
  info(recipe$Run$logger,
       "##### FORECAST ENSEMBLE MEAN PLOTS SAVED TO OUTPUT DIRECTORY #####")
}
