compute_workflow <- function(recipe, hcst, obs, fcst = NULL,
                             nchunks = chunk_indices) {
  # Load modules
  source("modules/Preprocessing/Preprocessing.R")
  source("modules/Units/Units.R")
  source("modules/Calibration/Calibration.R")
  source("modules/Anomalies/Anomalies.R")
  source("modules/Downscaling/Downscaling.R")
  source("modules/Skill/Skill.R")
  source("modules/Indices/Indices.R")

  # Define appender with custom log layout so that it knows not to append
  # within Compute().
  recipe$Run$logger <- log4r::logger(threshold = recipe$Run$Loglevel,
                                     appenders = list(console_appender(layout = .custom_log_layout())))
  modules <- tolower(strsplit(recipe$Run$startR_workflow$modules,
                              ", | |,")[[1]])
  # Create data list
  data <- list(hcst = hcst,
               obs = obs,
               fcst = fcst)
  data <- data[!sapply(data, is.null)]
  var_name <- as.vector(attributes(hcst)$FileSelectors[[1]]$var[[1]])
  # Remove duplicated objects
  rm(hcst, obs, fcst)
  # Pre-process datasets
  data <- preprocess_datasets(recipe, data)
  # Transform data to s2dv_cube
  data <- lapply(data, 
                 function(x) {
                   class(x) <- "startR_array"
                   as.s2dv_cube(x)
                 })
  for (cube_idx in seq(1:length(data))) {
    data[[cube_idx]]$attrs$Variable$varName <- var_name
  }
  # Define other outputs
  skill_metrics <- NULL
  probabilities <- NULL
  # Change units
  data <- Units(recipe, data, retrieve = F)
  # Loop over the modules
  for (module in modules) {
    if (module == "calibration") {
      data <- Calibration(recipe, data, retrieve = F)
    } else if (module == "anomalies") {
      data <- Anomalies(recipe, data, retrieve = F)
    } else if (module == "downscaling") {
      data <- Downscaling(recipe, data, retrieve = F)
    } else if (module == "skill") {
      skill_metrics <- Skill(recipe, data, retrieve = F, nchunks = nchunks)
    } else if (module == "probabilities") {
      ## TODO: Adapt probabilities output
      probabilities <- Probabilities(recipe, data, retrieve = F, nchunks = nchunks)
    } else if (module == "indices") {
      data <- Indices(recipe, data, retrieve = F)
    }
  }
  ## TODO: Define what to return depending the modules called + the recipe
  return_list <- list(hcst = data$hcst$data,
                      obs = data$obs$data,
                      fcst = data$fcst$data,
                      skill = skill_metrics,
                      probabilities = probabilities)
  # Eliminate NULL elements from the return list
  return_list <- return_list[!sapply(return_list, is.null)]
  return(return_list)
}

convert_to_s2dv_cube <- function(new_cube, original_cube) {
  attr(new_cube, "Variables") <- attr(original_cube, "Variables")
  attr(new_cube, "FileSelectors") <- attr(original_cube, "FileSelectors")
  attr(new_cube, "Files") <- attr(original_cube, "Files")

  class(new_cube) <- "startR_array"
  new_cube <- as.s2dv_cube(new_cube)
  return(new_cube)
}

run_compute_workflow <- function(recipe, data) {
  
  # ---------------------------------------------------------------------------
  # Step 1: Retrieve the modules that will be called inside the workflow

  modules <- tolower(strsplit(recipe$Run$startR_workflow$modules,
                              ", | |,")[[1]])
  
  # ---------------------------------------------------------------------------
  # Step 2: Define the inputs and outputs for Compute()
  # Retrieve original dimensions
  exp_dims <- names(attr(data$hcst, "Dimensions"))
  obs_dims <- names(attr(data$obs, "Dimensions"))
  # Remove the chunking dimensions from the original dimensions in the arrays,
  # to create the target dimensions
  exp_target_dims <- 
    exp_dims[!exp_dims %in% names(recipe$Run$startR_workflow$chunk_along)]
  obs_target_dims <-
    obs_dims[!obs_dims %in% names(recipe$Run$startR_workflow$chunk_along)]
  # Default inputs: hindcast and observations
  target_dims <- list(hcst = exp_target_dims,
                      obs = obs_target_dims)
  inputs <- list(hcst = data$hcst, obs = data$obs)
  # Define input attributes from Start()
  STARTR_CUBE_ATTRS <- c("Variables", "Dimensions", "FileSelectors", "Files")
  input_attributes <- list(hcst = STARTR_CUBE_ATTRS,
                           obs = STARTR_CUBE_ATTRS)
  # Add forecast if not empty
  if (!is.null(data$fcst)) {
    target_dims <- c(target_dims,
                     list(exp_target_dims))
    inputs <- c(inputs, list(fcst = data$fcst))
    input_attributes <- c(input_attributes, list(fcst = STARTR_CUBE_ATTRS))
  }

  # Create output dimensions
  ## TODO: Improve this condition
  if ("indices" %in% modules) {
    spatial_output_dims <- c("region")
  } else {
    spatial_output_dims <- c("latitude", "longitude")
  }
  exp_output_dims <- c(exp_target_dims[!exp_target_dims %in% c('var_dir',
                                                               'latitude',
                                                               'longitude')],
                       spatial_output_dims)
  # Default outputs: hindcast and observations
  output_dims <- list(hcst = exp_output_dims,
                      obs = exp_output_dims)
  # Add forecast if not empty
  if (!is.null(data$fcst)) {
    output_dims <- c(output_dims,
                     list(fcst = exp_output_dims))
  }
  # Add skill metrics if skill module is called
  if ("skill" %in% modules) {
    skill_dims <- c('metric', 'var', 'time', spatial_output_dims)
    skill_output_dims <-
      skill_dims[!skill_dims %in% names(recipe$Run$startR_workflow$chunk_along)]
    output_dims <- c(output_dims,
                     list(skill = skill_output_dims))
  }
  ## TODO: Add Indices (Niño1+2, Niño3, Niño3.4, Niño4, NAO)
  ## Instead of having the index dimension, we could add a second
  ## loop and then define outputs dynamically based on the recipe;
  ## one s2dv_cube per index. At least in the case of ENSO.
  # if ("indices" %in% modules) {
  #   idx_dims <- c('index', 'var', 'syear', 'time', 'region')
  # }

  # ---------------------------------------------------------------------------
  # Step 3: Generate the Step and call Compute()
  # Libraries to be loaded when running Compute()
  LIBS_LOAD <- c("log4r", "docopt", "ClimProjDiags", "multiApply", "yaml",
                 "s2dv", "abind", "easyNCDF", "CSTools", "lubridate", "PCICt",
                 "RColorBrewer", "configr", "sf", "ggplot2", "rnaturalearth",
                 "cowplot", "stringr", "pryr")

  step <- Step(fun = compute_workflow,
               target_dims = target_dims,
               output_dims = output_dims,
               use_attributes = input_attributes, 
               use_libraries = LIBS_LOAD)

  wf <- AddStep(inputs = inputs,
                step = step,
                recipe = recipe)

  #---------------------------------------
  ## TODO: Create function to generate call to Compute()?
  # Compute locally
  run_on <- recipe$Run$startR_workflow$run_on
  
  r_module_ver <- list(local = "R/4.1.2-foss-2015a-bare",
                       as_machine = "R/4.1.2-foss-2015a-bare",
                       nord3 = "R/4.1.2-foss-2019b")
  cdo_module_ver <- list(local = "CDO/1.9.8-foss-2015a",
                         as_machine = "CDO/1.9.8-foss-2015a",
                         nord3 = "CDO/1.9.8-foss-2019b")

  if (run_on == 'local') {
    # Compute locally, in serial
    res <- Compute(wf$hcst,
                   chunks = recipe$Run$startR_workflow$chunk_along)
  } else if (run_on %in% c('nord3', 'as_machine')) {
    #NOTE: autosubmit_suite_dir can be anywhere
    ## TODO: Change code_dir to autosubmit dir when necessary
    autosubmit_suite_dir <- recipe$Run$code_dir
    if (!is.null(recipe$Run$startR_workflow$slurm_directives)) {
      extra_queue_params <- as.list(recipe$Run$startR_workflow$slurm_directives)
    } else {
      extra_queue_params <- NULL
    }
    ## TODO: Review the core/thread setting
    # Compute in cluster/AS machine, in parallel, with autosubmit
    res <- Compute(wf$hcst,
                   chunks = recipe$Run$startR_workflow$chunk_along,
                   threads_compute = recipe$Analysis$ncores, 
                   threads_load = 2,
                   cluster = list(
                     queue_host = run_on, # name in platforms.yml
                     r_module = r_module_ver[[run_on]],
                     CDO_module = cdo_module_ver[[run_on]],
                     init_commands = list('module load GDAL PROJ GEOS'),
                     autosubmit_module = 'autosubmit/4.0.0b-foss-2015a-Python-3.7.3',
                     cores_per_job = recipe$Analysis$ncores,
                     job_wallclock = recipe$Run$startR_workflow$chunk_wallclock,
                     max_jobs = prod(unlist(recipe$Run$startR_workflow$chunk_along)),
                     polling_period = 10,
                     extra_queue_params = extra_queue_params,
                     expid = recipe$Run$startR_workflow$expid,
                     hpc_user = recipe$Run$startR_workflow$hpc_user,
                     run_dir = recipe$Run$code_dir
                   ),
                   workflow_manager = 'autosubmit', # 'ecFlow'
                   autosubmit_suite_dir = autosubmit_suite_dir,
                   autosubmit_server = NULL, #or 'bscesautosubmit02',
                   wait = TRUE
                   )
  }
  info(recipe$Run$logger, retrieve = TRUE,
       "##### COMPUTE SECTION ENDED, REFORMATTING OUTPUT #####")

  # ---------------------------------------------------------------------------
  # Step 4: Convert results to s2dv_cube/list format
  for (cube in c("hcst", "obs", "fcst")) {
    if (!is.null(res[[cube]])) {
      res$data[[cube]] <- convert_to_s2dv_cube(res[[cube]], data[[cube]])
    } else {
      res$data[[cube]] <- NULL
    }
    res[[cube]] <- NULL
  }
  ## TODO: Replicate for probabilities and other modules
  if (!is.null(res$skill)) {
    tmp_dir <- paste0(recipe$Run$output_dir, "/outputs/tmp/Skill/")
    metric_list <- readRDS(paste0(tmp_dir, "metric_names.Rds"))
    ## TODO: Fails if skill has 'region' instead of 'latlon'. Should handle 
    ##       dimensions dynamically. Can Subset() be used?
    res$skill <- lapply(seq(dim(res$skill)[1]),
                        function(x) {
                          Subset(res$skill,
                                 along = 'metric',
                                 indices = x,
                                 drop = 'selected')
                        })
    names(res$skill) <- metric_list
    # Put chunked metadata back together
    source("tools/retrieve_metadata.R")
    res$skill$metadata <- retrieve_metadata(tmp_dir = tmp_dir,
                                            chunks = recipe$Run$startR_workflow$chunk_along,
                                            array_dims = dim(res$skill[[1]]))
  }
  
  # ---------------------------------------------------------------------------
  # Step 5: Remove temporary files
  unlink(paste0(recipe$Run$output_dir, "/outputs/tmp/"), recursive = TRUE)

  # ---------------------------------------------------------------------------
  # Step 6: Save data
  ## PROBLEM: Defining the output dir
  # Saving(recipe = recipe, data = res$data, skill_metrics = res$skill)
  if (!is.null(res$skill) && recipe$Analysis$Workflow$Skill$save == 'all') {
    save_metrics(recipe = recipe, skill = res$skill,
                 agg = "global", module = "Skill")
  }
  # ---------------------------------------------------------------------------
  # Step 7: Return outputs
  res <- res[!sapply(res, is.null)]
  info(recipe$Run$logger, retrieve = TRUE,
       "##### DATA RETURNED AS A NAMED LIST #####")
  info(recipe$Run$logger, retrieve = TRUE,
       paste0("Steps performed: ", recipe$Run$startR_workflow$modules))
  return(res)
}
