source('~/s2dverification/R/Utils.R')
source('~/s2dverification/R/PlotLayout.R')
source('~/s2dverification/R/Subset.R')
library(ncdf4)
library(abind)
library(bigmemory)
library(future)
library(parallel)

# This function is a helper for the function .MergeArrays.
# It expects as inputs two named numeric vectors, and it extends them
# with dimensions of length 1 until an ordered common dimension
# format is reached.
.MergeArrayDims <- function(dims1, dims2) {
  new_dims1 <- c()
  new_dims2 <- c()
  while (length(dims1) > 0) {
    if (names(dims1)[1] %in% names(dims2)) {
      pos <- which(names(dims2) == names(dims1)[1])
      dims_to_add <- rep(1, pos - 1)
      if (length(dims_to_add) > 0) {
        names(dims_to_add) <- names(dims2[1:(pos - 1)])
      }
      new_dims1 <- c(new_dims1, dims_to_add, dims1[1])
      new_dims2 <- c(new_dims2, dims2[1:pos])
      dims1 <- dims1[-1]
      dims2 <- dims2[-c(1:pos)]
    } else {
      new_dims1 <- c(new_dims1, dims1[1])
      new_dims2 <- c(new_dims2, 1)
      names(new_dims2)[length(new_dims2)] <- names(dims1)[1]
      dims1 <- dims1[-1]
    }
  }
  if (length(dims2) > 0) {
    dims_to_add <- rep(1, length(dims2))
    names(dims_to_add) <- names(dims2)
    new_dims1 <- c(new_dims1, dims_to_add)
    new_dims2 <- c(new_dims2, dims2)
  }
  list(new_dims1, new_dims2)
}

# This function takes two named arrays and merges them, filling with
# NA where needed.
# dim(array1)
#          'b'   'c'         'e'   'f'
#           1     3           7     9
# dim(array2)
#    'a'   'b'         'd'         'f'   'g'
#     2     3           5           9     11
# dim(.MergeArrays(array1, array2, 'b'))
#    'a'   'b'   'c'   'e'   'd'   'f'   'g'
#     2     4     3     7     5     9     11
.MergeArrays <- function(array1, array2, along) {
  if (!(identical(names(dim(array1)), names(dim(array2))) &&
      identical(dim(array1)[-which(names(dim(array1)) == along)], 
                dim(array2)[-which(names(dim(array2)) == along)]))) {
    new_dims <- .MergeArrayDims(dim(array1), dim(array2))
    dim(array1) <- new_dims[[1]]
    dim(array2) <- new_dims[[2]]
    for (j in 1:length(dim(array1))) {
      if (names(dim(array1))[j] != along) {
        if (dim(array1)[j] != dim(array2)[j]) {
          if (which.max(c(dim(array1)[j], dim(array2)[j])) == 1) {
            na_array_dims <- dim(array2)
            na_array_dims[j] <- dim(array1)[j] - dim(array2)[j]
            na_array <- array(dim = na_array_dims)
            array2 <- abind(array2, na_array, along = j)
            names(dim(array2)) <- names(na_array_dims)
          } else {
            na_array_dims <- dim(array1)
            na_array_dims[j] <- dim(array2)[j] - dim(array1)[j]
            na_array <- array(dim = na_array_dims)
            array1 <- abind(array1, na_array, along = j)
            names(dim(array1)) <- names(na_array_dims)
          }
        }
      }
    }
  }
  array1 <- abind(array1, array2, along = which(names(dim(array1)) == along))
  names(dim(array1)) <- names(dim(array2))
  array1
}

.CheckSelectors <- function(selectors, var = NULL, return_indices = TRUE) {
  if (length(selectors) == 0) {
    stop("No selectors provided in 'selectors'.")
  }
  if (return_indices) {
    if (is.list(selectors)) {
      if (length(selectors) != 2) {
        stop("'selectors' provided in a wrong format.")
      }
      for (i in 1:length(selectors)) {
        if (is.null(var)) {
          stop("No selector values provided in 'var'.")
        } else if (is.na(selectors[[i]])) {
          if (i == 1) {
            selectors[[i]] <- 1
          } else {
            selectors[[i]] <- length(var)
          }
        } else if (is.character(selectors[[i]])) {
          if (is.character(var)) {
            candidate <- which(var == selectors[[i]])
            if (length(candidate) > 0) {
              selectors[[i]] <- candidate[1]
            } else {
              stop("Selector value not found in 'var'.")
            }
          } else {
            stop("Character selectors provided but possible values in 'var' are not character.")
          }
        } else if (is.numeric(selectors[[i]])) {
          if (is.numeric(var)) {
            if (sum(var == selectors[[i]], na.rm = TRUE) > 0) {
              selectors[[i]] <- which(var == selectors[[i]])[1]
            } else {
              .warning(paste0("A numeric selector has been ",
                       "provided for a dimension defined along a ",
                       "numeric variable, but no exact match ",
                       "found. Taking the index of the nearest ",
                       "value."))
              selectors[[i]] <- which.min(abs(var - selectors[[i]]))
            }
          } else {
            stop("Numeric selectors provided but possible values in 'var' are not numeric.")
          }
        }
      }
      selectors[[1]]:selectors[[2]]
    } else if (is.numeric(selectors)) {
      if (is.null(var)) {
        selectors
      } else {
        if (is.numeric(var)) {
          if (!all(selectors %in% var)) {
            .warning(paste0("Numeric selectors have been ",
                     "provided for a dimension defined along a ",
                     "numeric variable, but no exact match ",
                     "found for all the selectors. Taking the index of the ",
                     "nearest values."))
          }
          sapply(selectors, function(x) which.min(abs(var - x))[1])
        } else {
          stop("Numeric selectors provided but possible values in 'var' are not numeric.")
        }
      }
    } else if (any(c('POSIXct', 'POSIXlt', 'POSIXt', 'Date') %in% class(selectors))) {
      if (is.null(var)) {
        stop("Numeric selectors have been provided for a dimension ",
             "defined along a date variable, but no possible values ",
             "provided in 'var'.")
      }
      if (!all(selectors %in% var)) {
        .warning(paste0("Date selectors have been ",
                 "provided for a dimension defined along a ",
                 "date variable, but no exact match ",
                 "found for all the selectors. Taking the index of the ",
                 "nearest values."))
      }
      sapply(selectors, function(x) which.min(abs(var - x))[1])
    } else {
      if (is.null(var)) {
        stop("No selector values provided in 'var'.")
      } else {
        if ((length(selectors) == 1) && 
            (selectors %in% c('all', 'first', 'last'))) {
          if (selectors == 'all') {
            1:length(var)
          } else if (selectors == 'first') {
            1
          } else {
            length(var)
          }
        } else {
          if (!identical(class(var), class(selectors))) {
            stop("Class of provided selectors does not match class of 'var'.")
          }
          candidates <- which(var == selectors)
          if (length(candidates) == 0) {
            stop("Selectors do not match values in 'var'.")
          } else if (length(candidates) != length(selectors)) {
            stop("Some selectors do not match values in 'var'.")
          }
          candidates
        }
      }
    }
  } else {
    if (is.list(selectors)) {
    } else if (is.numeric(selectors)) {
      if (length(selectors) > 0) {
        if (!is.null(var)) {
          var[selectors]
        }
      } else {
        stop("No selectors provided.")
      }
    } else {
      if ((length(selectors) == 1) && 
          (selectors %in% c('all', 'first', 'last'))) {
        if (selectors == 'all') {
          var
        } else if (selectors == 'first') {
          head(var, 1)
        } else {
          tail(var, 1)
        }
      } else {
        selectors
      }
    }
  }
}

.NcOpener <- function(file_path) {
  result <- NULL
  
  try({
    result <- nc_open(file_path)
  }, silent = TRUE)
  
  result
}

.NcVarReader <- function(file_path = NULL, file_object = NULL, 
                         var_name = NULL, file_opener = NULL,
                         file_closer = NULL) {
  close <- FALSE
  if (is.null(file_object)) {
    if (is.null(file_path)) {
      stop("Either 'file_path' or 'file_object' must be provided.")
    }
    if (is.null(file_opener) || is.null(file_closer)) {
      stop("Both 'file_opener' and 'file_closer' must be provided if ",
           "no 'file_object' is directly provided.")
    } else if (!is.function(file_opener) || !is.function(file_closer)) {
      stop("Parameters 'file_opener' and 'file_closer' must be functions.")
    }
    file_object <- file_opener(file_path)
    close <- !is.null(file_object)
  } else {
    file_path <- file_object$filename
  }

  if (is.null(var_name)) {
    if (length(fnc$var) == 0) {
      stop(paste0("No variables found in ", file_path, "."))
    } else if (length(fnc$var) > 1) {
      stop("A 'var_name' must be provided if the file contains more than one variables.")
    } else {
      var_name <- names(fnc$var)[1]
    }
  }

  result <- NULL
  if (!is.null(file_object)) {
    # Create all variables that are 'dimvars'
    extra_dimvars <- NULL
    for (dim_name in names(file_object$dim)) {
      if (file_object$dim[[dim_name]]$create_dimvar) {
        new_var <- list(name = dim_name, ndims = 1, 
                        size = file_object$dim[[dim_name]]$len,
                        units = file_object$dim[[dim_name]]$units,
                        dim = list(file_object$dim[[dim_name]]))
        file_object$var[[dim_name]] <- new_var
        file_object$nvars <- file_object$nvars + 1
        extra_dimvars <- c(extra_dimvars, dim_name)
      }
    }
    try({
      if (var_name %in% extra_dimvars) {
        result <- file_object$dim[[var_name]]$vals
      } else { 
        result <- ncvar_get(file_object, var_name)
      }
      units <- file_object$var[[var_name]]$units
      if (units %in% c('seconds', 'minutes', 'hours', 'days', 'weeks', 'months', 'years')) {
        if (units == 'seconds') {
          units <- 'secs'
        } else if (units == 'minutes') {
          units <- 'mins'
        }
        result[] <- paste(result, units)
      } else if (grep(' since ', units)) {
        parts <- strsplit(units, ' since ')[[1]]
        units <- parts[1]
        if (units == 'seconds') {
          units <- 'secs'
        } else if (units == 'minutes') {
          units <- 'mins'
        }
        result <- seq(as.POSIXct(parts[2]), length = max(result, na.rm = TRUE) + 1, by = units)[result + 1]
      }
      names(dim(result)) <- sapply(file_object$var[[var_name]]$dim, '[[', 'name')
    }, silent = TRUE)
  }

  if (close) {
    file_closer(file_object)
  }
  result
}

# Parameter 'file_selectors' expects a named character vector of single 
# file dimension selectors.
# Parameter 'inner_selectors' expects a named list of numeric or 
# character string vectors.
.NcDimReader <- function(file_path = NULL, file_object = NULL, 
                         file_selectors = NULL, inner_indices = NULL,
                         file_opener = NULL, file_closer = NULL) {
  close <- FALSE
  if (is.null(file_object)) {
    if (is.null(file_path)) {
      stop("Either 'file_path' or 'file_object' must be provided.")
    }
    if (is.null(file_opener) || is.null(file_closer)) {
      stop("Both 'file_opener' and 'file_closer' must be provided if ",
           "no 'file_object' is directly provided.")
    } else if (!is.function(file_opener) || !is.function(file_closer)) {
      stop("Parameters 'file_opener' and 'file_closer' must be functions.")
    }
    file_object <- file_opener(file_path)
    close <- !is.null(file_object)
  } else {
    file_path <- file_object$filename
  }

  dims <- NULL
  if (!is.null(file_object)) {
    extra_dimvars <- NULL
    # Create all variables that are 'dimvars'
    for (dim_name in names(file_object$dim)) {
      if (file_object$dim[[dim_name]]$create_dimvar) {
        new_var <- list(name = dim_name, ndims = 1, 
                        size = file_object$dim[[dim_name]]$len,
                        units = file_object$dim[[dim_name]]$units,
                        dim = list(file_object$dim[[dim_name]]))
        file_object$var[[dim_name]] <- new_var
        file_object$nvars <- file_object$ncars + 1
        extra_dimvars <- c(extra_dimvars, dim_name)
      }
    }
    var_names <- NULL
    var_dim_name <- NULL
    ## TODO: Forbid combination of both 'var' and 'variable' in .Load
    if (any(c('var', 'variable') %in% c(names(file_selectors), names(inner_indices)))) {
      var_dim_name <- c('var', 'variable')[which(c('var', 'variable') %in% c(names(file_selectors), names(inner_indices)))]
      if (var_dim_name %in% names(file_selectors)) {
        if (is.character(file_selectors[var_dim_name])) {
          var_names <- file_selectors[var_dim_name]
        }
      } else if (var_dim_name %in% names(inner_indices)) {
        var_names <- names(file_object$var)
        if (is.character(inner_indices[[var_dim_name]])) {
          if ((length(inner_indices[[var_dim_name]]) == 1) &&
              (inner_indices[[var_dim_name]] %in% c('all', 'first', 'last'))) {
            if (inner_indices[[var_dim_name]] == 'first') {
              var_names <- head(var_names, 1)
            } else if (inner_indices[[var_dim_name]] == 'last') {
              var_names <- tail(var_names, 1)
            }
          } else {
            var_names <- inner_indices[[var_dim_name]][which(inner_indices[[var_dim_name]] %in% var_names)]
          }
        } else if (is.numeric(inner_indices[[var_dim_name]])) {
          if (any(inner_indices[[var_dim_name]] > length(var_names)) ||
              any(inner_indices[[var_dim_name]] < 0)) {
            stop("Selectors out of range for '", var_dim_name, "'.")
          }
          var_names <- var_names[inner_indices[[var_dim_name]]]
        } else {
          stop("Selectors for '", var_dim_name, "' must be numeric or ",
               "character strings.")
        }
      }
    }
    if (is.null(var_names)) {
      if ((length(file_object$var) == 1)) {
        var_names <- names(file_object$var)[1]
      } else {
        stop(paste0("Could not disambiguate which variable in the file ",
                    "is being requested. Either provide the parameter ",
                    "'var'/'variable' or force the file to contain only ",
                    "one variable.\n", file_path))
      }
    }
    dims <- NULL
    for (var_name in var_names) {
      if (var_name %in% names(file_object$var)) {
        found_dims <- file_object$var[[var_name]]$size
        names(found_dims) <- sapply(file_object$var[[var_name]]$dim, '[[', 'name')
        if (!is.null(dims)) {
          dims <- .MergeArrayDims(dims, found_dims)
          #tmp1 <- dims[sort(names(dims))]
          #tmp1 <- tmp1[-which(tmp1 == 1)]
          #tmp2 <- found_dims[sort(names(found_dims))]
          #tmp2 <- tmp2[-which(tmp2 == 1)]
          #if (!identical(tmp1, tmp2)) {
          #  stop("Requested variables in a same file with different ",
          #       "dimensions. This is not supported.")
          #}
        } else {
          dims <- found_dims
        }
      } else {
        stop("Could not find the variable '", var_name, "' in the ",
             "file ", file_path)
      }
    }
    if (!is.null(var_dim_name) && (var_dim_name %in% names(inner_indices))) {
      new_dim <- length(file_object$var)
      names(new_dim) <- var_dim_name
      dims <- c(new_dim, dims)
    }
  }

  if (close) {
    file_closer(file_object)
  }

  dims
}

# Parameter 'file_selectos' expects a named character vector of single
# file dimension selectors.
# Parameter 'inner_selectors' expects a named list of numeric vectors.
.NcDataReader <- function(file_path = NULL, file_object = NULL, 
                          file_selectors = NULL, inner_indices,
                          file_opener = NULL, file_closer = NULL,
                          file_dim_reader) {
#print("aa")
  close <- FALSE
  if (is.null(file_object)) {
    if (is.null(file_path)) {
      stop("Either 'file_path' or 'file_object' must be provided.")
    }
    if (is.null(file_opener) || is.null(file_closer)) {
      stop("Both 'file_opener' and 'file_closer' must be provided if ",
           "no 'file_object' is directly provided.")
    } else if (!is.function(file_opener) || !is.function(file_closer)) {
      stop("Parameters 'file_opener' and 'file_closer' must be functions.")
    }
    file_object <- file_opener(file_path)
    close <- !is.null(file_object)
  } else {
    file_path <- file_object$filename
  }

  result <- NULL
  if (!is.null(file_object)) {
    var_names <- NULL
    var_dim_name <- NULL
    if (any(c('var', 'variable') %in% c(names(file_selectors), names(inner_indices)))) {
      var_dim_name <- c('var', 'variable')[which(c('var', 'variable') %in% c(names(file_selectors), names(inner_indices)))]
      if (var_dim_name %in% names(file_selectors)) {
        if (is.character(file_selectors[[var_dim_name]])) {
          var_names <- file_selectors[[var_dim_name]]
        }
      } else if (var_dim_name %in% names(inner_indices)) {
        var_names <- names(file_object$var)
        if (is.character(inner_indices[[var_dim_name]])) {
          var_names <- inner_indices[[var_dim_name]][which(inner_indices[[var_dim_name]] %in% var_names)]
        } else if (is.numeric(inner_indices[[var_dim_name]])) {
          if (any(inner_indices[[var_dim_name]] > length(var_names)) ||
              any(inner_indices[[var_dim_name]] < 0)) {
            stop("Selectors out of range for '", var_dim_name, "'.")
          }
          var_names <- var_names[inner_indices[[var_dim_name]]]
        } else {
          stop("Selectors for '", var_dim_name, "' must be numeric or ",
               "character strings.")
        }
      }
    }
    if (is.null(var_names)) {
      if (length(file_object$var) == 1) {
        var_names <- names(file_object$var)[1]
      } else {
        stop(paste0("Could not disambiguate which variable in the file ",
                    "is being requested. Either provide the parameter ",
                    "'var'/'variable' or force the file to contain only ",
                    "one variable.\n", file_path))
      }
    }
    result <- try({
      res <- NULL
      dims <- file_dim_reader(file_path, file_object, file_selectors,
                              inner_indices)
#print("bb")
      if (!all(names(inner_indices) %in% names(dims))) {
        stop("Missing dimensions in the file.\nExpected: ",
             paste(names(inner_indices), collapse = ', '), "\n",
             "Found: ", paste(names(dims), collapse = ', '), "\n",
             file_path)
      }
      extra_dims <- NULL
      if (length(dims) > length(inner_indices)) {
        common_dims <- which(names(dims) %in% names(inner_indices))
        if (length(common_dims) > 0) {
          extra_dims <- dims[-common_dims]
        } else {
          extra_dims <- dims
        }
        if (any(extra_dims != 1)) {
          stop("Unexpected extra dimensions (of length > 1) in the file.\nExpected: ",
               paste(names(inner_indices), collapse = ', '), "\n",
               "Found: ", paste(names(dims), collapse = ', '), "\n",
               file_path)
        } else {
          inner_indices[names(extra_dims)] <- rep(1, length(extra_dims))
        }
        extra_dims <- names(extra_dims)
      }
      # Here we are allowing for indices out of range (simply discarding them).
      for (inner_dim in names(inner_indices)) {
        inds_out_of_range <- which(inner_indices[[inner_dim]] > dims[inner_dim])
        if (length(inds_out_of_range) > 0) {
          inner_indices[[inner_dim]] <- inner_indices[[inner_dim]][-which(inner_indices[[inner_dim]] > dims[inner_dim])]
        }
        if (any(inner_indices[[inner_dim]] < 0)) {
          stop("Invalid indices provided for '", inner_dim, "'.")
        }
      }
      missing_dims <- NULL
      if (length(dims) < length(inner_indices)) {
        missing_dim_names <- names(inner_indices)[-which(names(inner_indices) %in% names(dims))]
        missing_dim_indices <- lapply(missing_dim_names, function(x) inner_indices[[x]])
        if (any(!sapply(missing_dim_indices, identical, 1))) {
          stop("Could not find all expected dimensions in the file.\nExpected: ",
               paste(names(inner_indices), collapse = ', '), "\n",
               "Found: ", paste(names(dims), collapse = ', '), "\n",
               file_path)
        } else {
          original_dims <- sapply(inner_indices, length)
          names(original_dims) <- names(inner_indices)
          inner_indices <- inner_indices[-which(names(inner_indices) %in% missing_dim_names)]
        }
        missing_dims <- missing_dim_names
      }
#print("cc")
      inner_dims <- names(inner_indices)
      reorder <- NULL
      if (any(names(dims) != inner_dims)) {
        reorder <- sapply(names(dims), function(x) which(inner_dims == x))
        reorder_back <- sapply(inner_dims, function(x) which(names(dims) == x))
        inner_indices <- inner_indices[reorder]
      }
      if (!is.null(var_dim_name) && (var_dim_name %in% names(inner_indices))) {
        inner_indices <- inner_indices[-which(names(inner_indices) == var_dim_name)]
      }
      for (var_name in var_names) {
        tmp <- do.call('[', c(list(ncvar_get(file_object, var_name, sapply(inner_indices, min),
                                             sapply(inner_indices, max) - sapply(inner_indices, min) + 1,
                                             collapse_degen = FALSE)),
                              lapply(inner_indices, function(x) x - min(x) + 1), list(drop = FALSE)))
        if (!is.null(var_dim_name) && (var_dim_name %in% inner_dims)) {
          dim(tmp) <- c(dim(tmp)[which(1:length(dim(tmp)) < which(inner_dims == var_dim_name))], 1,
                        dim(tmp)[which(1:length(dim(tmp)) >= which(inner_dims == var_dim_name))])
          if (is.null(res)) {
            res <- tmp
          } else {
            res <- abind(res, tmp, along = which(inner_dims == var_dim_name))
          }
        } else {
          res <- tmp
        }
      }
      if (!is.null(reorder)) {
        res <- aperm(res, reorder_back)
      }
      if (!is.null(missing_dims)) {
        dim(res) <- original_dims
      }
      if (!is.null(extra_dims)) {
        dim(res) <- dim(res)[-which(inner_dims %in% extra_dims)]
      }
#print("dd")
      res
    })
    if ('try-error' %in% class(result)) {
      result <- NULL
    }
  }

  if (close) {
    file_closer(file_object)
  }

  result
}

.NcCloser <- function(file_object) {
  result <- NULL

  try({
    result <- nc_close(file_object)
  })

  invisible(result)
}

.Load <- function(..., pattern_dims = NULL, 
                  depending_file_dims = NULL,       # ex list(chunk = 'sdate')
                  inner_dims_across_files = NULL,   # ex list(ftime = 'chunk')
                  return_vars = NULL,               # ex list('ftime_var', 'lat_var', lon = c('dataset'), date = c('dataset', 'member', 'sdate'))
                  selector_checker  = .CheckSelectors,
                  file_opener = .NcOpener,
                  file_var_reader = .NcVarReader,
                  file_dim_reader = .NcDimReader,
                  file_data_reader = .NcDataReader,
                  file_closer = .NcCloser,
                  num_procs = NULL, silent = FALSE) {
                  #, config_file = NULL
                  #dictionary_dim_names = ,
                  #dictionary_var_names =  
  dim_params <- list(...)
  # Split *_var parameters
  var_params_ind <- grep('_var$', names(dim_params))
  var_params <- dim_params[var_params_ind]
  # Check all *_var are NULL or vectors of character strings, and 
  # that they all have a matching dimension param.
  i <- 1
  for (var_param in var_params) {
    if (!is.character(var_param)) {
      stop("All '*_var' parameters must be character strings.")
    } else if (!any(grepl(paste0('^', strsplit(names(var_params)[i], 
                                               '_var$')[[1]][1], '$'),
                          names(dim_params)))) {
      stop(paste0("All '*_var' parameters must be associated to a dimension parameter. Found parameter '", 
                  names(var_params)[i], "' but no parameter '", 
                  strsplit(names(var_params)[i], '_var$')[[1]][1], "'."))
    }
    i <- i + 1
  }
  # Split *_checker parameters
  check_params_ind <- grep('_checker$', names(dim_params))
  check_params <- dim_params[check_params_ind]
  # Check all *_checker are NULL or functions, and that they all have 
  # a matching dimension param.
  i <- 1
  for (check_param in check_params) {
    if (!is.function(check_param)) {
      stop("All '*_checker' parameters must be functions.")
    } else if (!any(grepl(paste0('^', strsplit(names(check_params)[i], 
                                               '_checker$')[[1]][1], '$'),
                          names(dim_params)))) {
      stop(paste0("All '*_checker' parameters must be associated to a dimension parameter. Found parameter '", 
                  names(check_params)[i], "' but no parameter '", 
                  strsplit(names(check_params)[i], '_checker$')[[1]][1], "'."))
    }
    i <- i + 1
  }
  # Leave alone the dimension parameters
  if (length(c(var_params_ind, check_params_ind)) > 0) {
    dim_params <- dim_params[-c(var_params_ind, check_params_ind)]
  }
  dim_names <- names(dim_params)
  if (is.null(dim_names)) {
    stop("At least one pattern dim must be specified.")
  }
  # Make the keys of 'var_params' and 'check_params' to be the name of 
  # the corresponding dimension.
  names(var_params) <- gsub('_var$', '', names(var_params))
  names(check_params) <- gsub('_checker$', '', names(check_params))

  # Check pattern_dims
  if (is.null(pattern_dims)) {
    .warning(paste0("Parameter 'pattern_dims' not specified. Taking the first dimension, '", 
                    dim_names[1], "' as 'pattern_dims'."))
    pattern_dims <- dim_names[1]
  } else if (is.character(pattern_dims) && (length(pattern_dims) > 0)) {
    pattern_dims <- unique(pattern_dims)
  } else {
    stop("Parameter 'pattern_dims' must be a vector of character strings.")
  }
  if (any(names(var_params) %in% pattern_dims)) {
    stop("'*_var' parameters specified for pattern dimensions. Remove or fix them.")
  }
  # Find the pattern dimension with the pattern specifications
  found_pattern_dim <- NULL
  for (pattern_dim in pattern_dims) {
    # Check all specifications in pattern_dim are valid
    dat <- datasets <- dim_params[[pattern_dim]]
    if (is.null(dat) || !(is.character(dat) && all(nchar(dat) > 0)) && !is.list(dat)) {
      stop(paste0("Parameter '", pattern_dim, 
                  "' must be a list of lists with pattern specifications or a vector of character strings."))
    }
    if (!is.null(check_params[[pattern_dim]])) {
      .warning(paste0("A checker for the selectors of '", pattern_dim, 
                      "' has been specified, but it is a pattern dimension and the checker will be ignored."))
    }
    if (any(sapply(dat, is.list))) {
      if (is.null(found_pattern_dim)) {
        found_pattern_dim <- pattern_dim
      } else {
        stop("Found more than one pattern dim with pattern specifications (list of lists). One and only one pattern dim must contain pattern specifications.")
      }
    }
  }
  if (is.null(found_pattern_dim)) {
    .warning(paste0("Taking the first pattern dim, '", pattern_dims[1], "', as dimension with pattern specifications (to be fetched in configuration file)."))
    found_pattern_dim <- pattern_dims[1]
  }
  # Once the pattern dimension with dataset specifications is found,
  # the variable 'dat' is mounted with the information of each
  # dataset.
  dat <- datasets <- dim_params[[found_pattern_dim]]
  dat_info_names <- c('name', 'path')#, 'nc_var_name', 'suffix', 'var_min', 'var_max', 'dimnames')
  dat_to_fetch <- c()
  dat_names <- c()
  if (!is.list(dat)) {
    dat <- as.list(dat)
  }
  for (i in 1:length(dat)) {
    if (is.character(dat[[i]]) && length(dat[[i]]) == 1 && nchar(dat[[i]]) > 0) {
      if (grepl('^(\\./|\\.\\./|/.*/|~/)', dat[[i]])) {
        dat[[i]] <- list(path = dat[[i]])
      } else {
        dat[[i]] <- list(name = dat[[i]])
      }
    } else if (!is.list(dat[[i]])) {
      stop(paste0("Parameter '", pattern_dim, 
                  "' is incorrect. It must be a list of lists or character strings."))
    }
    #if (!(all(names(dat[[i]]) %in% dat_info_names))) {
    #  stop("Error: parameter 'dat' is incorrect. There are unrecognized components in the information of some of the datasets. Check 'dat' in ?Load for details.")
    #}
    if (!('name' %in% names(dat[[i]]))) {
      dat[[i]][['name']] <- paste0('dat', i)
      if (!('path' %in% names(dat[[i]]))) {
        stop(paste0("Parameter '", found_pattern_dim, 
                    "' is incorrect. A 'path' should be provided for each dataset if no 'name' is provided."))
      }
    } else if (!('path' %in% names(dat[[i]]))) {
      dat_to_fetch <- c(dat_to_fetch, i)
    }
    #if ('path' %in% names(dat[[i]])) {
    #  if (!('nc_var_name' %in% names(dat[[i]]))) {
    #    dat[[i]][['nc_var_name']] <- '$var_name$'
    #  }
    #  if (!('suffix' %in% names(dat[[i]]))) {
    #    dat[[i]][['suffix']] <- ''
    #  }
    #  if (!('var_min' %in% names(dat[[i]]))) {
    #    dat[[i]][['var_min']] <- ''
    #  }
    #  if (!('var_max' %in% names(dat[[i]]))) {
    #    dat[[i]][['var_max']] <- ''
    #  }
    #}
    dat_names <- c(dat_names, dat[[i]][['name']])
  }
  if ((length(dat_to_fetch) > 0) && (length(dat_to_fetch) < length(dat))) {
    .warning("'path' has been provided for some datasets. Any information in the configuration file related to these will be ignored.")
  }

  # Check depending_file_dims (it is mostly checked later).
  if (!is.null(depending_file_dims) && (!is.list(depending_file_dims) || 
      !all(is.character(unlist(depending_file_dims))) ||
      !all(sapply(unlist(depending_file_dims), length) == 1))) {
    stop("Parameter 'depending_file_dims' must be a named list where ",
         "the values are a single character string.")
  }

  # Check inner_dims_across_files (it is mostly checked later).
  if (!is.null(inner_dims_across_files)) {
    if (!is.list(inner_dims_across_files) || 
        !all(is.character(unlist(inner_dims_across_files))) ||
        !all(sapply(unlist(inner_dims_across_files), length) == 1)) {
      stop("Parameter 'inner_dims_across_files' must be a named list ",
           "where the values are a single character string.")
    }
    # Reorder: example, convert list(ftime = 'chunk', ensemble = 'member', xx = 'chunk')
    #                        to list(chunk = c('ftime', 'xx'), member = 'ensemble')
    new_idaf <- list()
    for (i in names(inner_dims_across_files)) {
      if (!(inner_dims_across_files[[i]] %in% names(new_idaf))) {
        new_idaf[[inner_dims_across_files[[i]]]] <- i
      } else {
        new_idaf[[inner_dims_across_files[[i]]]] <- c(new_idaf[[inner_dims_across_files[[i]]]], i)
      }
    }
    inner_dims_across_files <- new_idaf
  }

  # Check return_vars
  if (is.null(return_vars)) {
    return_vars <- list()
#    if (length(var_params) > 0) {
#      return_vars <- as.list(paste0(names(var_params), '_var'))
#    } else {
#      return_vars <- list()
#    }
  }
  if (!is.list(return_vars)) {
    stop("Parameter 'return_vars' must be a list or NULL.")
  }
  if (length(return_vars) > 0 && is.null(names(return_vars))) {
#    names(return_vars) <- rep('', length(return_vars))
    stop("Parameter 'return_vars' must be a named list.")
  }
  i <- 1
  while (i <= length(return_vars)) {
#    if (names(return_vars)[i] == '') {
#      if (!(is.character(return_vars[[i]]) && (length(return_vars[[i]]) == 1))) {
#        stop("The ", i, "th specification in 'return_vars' is malformed.")
#      } 
#      if (!grepl('_var$', return_vars[[i]])) {
#        stop("The ", i, "th specification in 'return_vars' is malformed.")
#      }
#      dim_name <- strsplit(return_vars[[i]], '_var$')[[1]][1]
#      if (!(dim_name %in% names(var_params))) {
#        stop("'", dim_name, "_var' requested in 'return_vars' but ",
#             "no '", dim_name, "_var' specified in the .Load call.")
#      }
#      names(return_vars)[i] <- var_params[[dim_name]]
#      return_vars[[i]] <- found_pattern_dim
#    } else
    if (length(return_vars[[i]]) > 0) { 
      if (!is.character(return_vars[[i]])) {
        stop("The ", i, "th specification in 'return_vars' is malformed. It ",
             "must be a vector of character strings of valid file dimension ",
             "names.")
      }
    }
    i <- i + 1
  }

  # Check selector_checker
  if (is.null(selector_checker) || !is.function(selector_checker)) {
    stop("Parameter 'selector_checker' must be a function.")
  }

  # Check file_opener
  if (is.null(file_opener) || !is.function(file_opener)) {
    stop("Parameter 'file_opener' must be a function.")
  }

  # Check file_var_reader
  if (!is.null(file_var_reader) && !is.function(file_var_reader)) {
    stop("Parameter 'file_var_reader' must be a function.")
  }

  # Check file_dim_reader
  if (!is.null(file_dim_reader) && !is.function(file_dim_reader)) {
    stop("Parameter 'file_dim_reader' must be a function.")
  }

  # Check file_data_reader
  if (is.null(file_data_reader) || !is.function(file_data_reader)) {
    stop("Parameter 'file_data_reader' must be a function.")
  }

  # Check file_closer
  if (is.null(file_closer) || !is.function(file_closer)) {
    stop("Parameter 'file_closer' must be a function.")
  }

  # Check num_procs
  if (!is.null(num_procs)) {
    if (!is.numeric(num_procs)) {
      stop("Parameter 'num_procs' must be numeric.")
    } else {
      num_procs <- round(num_procs)
    }
  }

  # Check silent
  if (!is.logical(silent)) {
    stop("Parameter 'silent' must be logical.")
  }

  dim_params[[found_pattern_dim]] <- dat_names

  # Check that no unrecognized variables are present in the path patterns
  # and also that no file dimensions are requested to THREDDs catalogs.
  # And in the mean time, build all the work pieces and look for the 
  # first available file of each dataset.
  array_of_files_to_load <- NULL
  array_of_not_found_files <- NULL
  indices_of_first_files_with_data <- vector('list', length(dat))
  selectors_of_first_files_with_data <- vector('list', length(dat))
  found_file_dims <- vector('list', length(dat))
  expected_inner_dims <- vector('list', length(dat))
  dataset_has_files <- rep(FALSE, length(dat))
#print("A")
  for (i in 1:length(dat)) {
#print("B")
    dat_selectors <- dim_params
    dat_selectors[[found_pattern_dim]] <- dat_selectors[[found_pattern_dim]][i]
    dim_vars <- paste0('$', dim_names, '$')
    file_dims <- dim_names[which(sapply(dim_vars, grepl, dat[[i]][['path']], fixed = TRUE))]
    file_dims <- unique(c(pattern_dims, file_dims))
    found_file_dims[[i]] <- file_dims
    expected_inner_dims[[i]] <- dim_names[which(!(dim_names %in% file_dims))]
    # (Check the depending_file_dims).
    if (any(c(names(depending_file_dims), unlist(depending_file_dims)) %in% 
            expected_inner_dims[[i]])) {
      stop(paste0("The dimension dependancies specified in ",
                  "'depending_file_dims' can only be between file ",
                  "dimensions, but some inner dimensions found in ",
                  "dependancies for '", dat[[i]][['name']], "', which ",
                  "has the following file dimensions: ", 
                  paste(paste0("'", file_dims, "'"), collapse = ', '), ".")) 
    } else {
      a <- names(depending_file_dims) %in% file_dims
      b <- unlist(depending_file_dims) %in% file_dims
      ab <- a & b
      if (any(!ab)) {
        .warning("Detected some dependancies in 'depending_file_dims' with ",
                 "non-existing dimension names. These will be disregarded.")
        depending_file_dims <- depending_file_dims[-which(!ab)]
      }
      if (any(names(depending_file_dims) == unlist(depending_file_dims))) {
        depending_file_dims <- depending_file_dims[-which(names(depending_file_dims) == unlist(depending_file_dims))]
      }
    }
    # (Check the inner_dims_across_files).
    if (any(!(names(inner_dims_across_files) %in% file_dims)) ||
        any(!(unlist(inner_dims_across_files) %in% expected_inner_dims[[i]]))) {
      stop(paste0("All relationships specified in ",
                  "'inner_dims_across_files' must be between a inner ",
                  "dimension and a file dimension. Found wrong ",
                  "specification for '", dat[[i]][['name']], "', which ",
                  "has the following file dimensions: ", 
                  paste(paste0("'", file_dims, "'"), collapse = ', '),
                  ", and the following inner dimensions: ", 
                  paste(paste0("'", expected_inner_dims[[i]], "'"), 
                        collapse = ', '), "."))
    }
    # (Check the return_vars).
    j <- 1
    while (j <= length(return_vars)) {
      if (any(!(return_vars[[j]] %in% file_dims))) {
        if (any(return_vars[[j]] %in% expected_inner_dims[[i]])) {
          stop("Found variables in 'return_vars' requested ",
               "for some inner dimensions (for dataset '",
               dat[[i]][['name']], "'), but variables can only be ",
               "requested for file dimensions.")
        } else {
          stop("Found variables in 'return_vars' requested ",
               "for non-existing dimensions.")
        }
      }
      j <- j + 1
    }
    # (Check the *_var parameters).
    if (any(!(unlist(var_params) %in% names(return_vars)))) {
      stop("All '*_var' params must associate a dimension to one of the ",
           "requested variables in 'return_vars'.")
    } 
    replace_values <- vector('list', length = length(file_dims))
    names(replace_values) <- file_dims
    # Take the first selector for all possible file dimensions
    for (file_dim in file_dims) {
      if (file_dim %in% names(var_params)) {
        .warning(paste0("The '", file_dim, "_var' param will be ignored since '", 
                        file_dim, "' is a file dimension (for the dataset with pattern ", 
                        dat[[i]][['path']], ")."))
      }
      if (!is.list(dat_selectors[[file_dim]]) || 
          (is.list(dat_selectors[[file_dim]]) && 
           length(dat_selectors[[file_dim]]) == 2 &&
           is.null(names(dat_selectors[[file_dim]])))) {
        dat_selectors[[file_dim]] <- list(dat_selectors[[file_dim]])
      }
      first_class <- class(dat_selectors[[file_dim]][[1]])
      first_length <- length(dat_selectors[[file_dim]][[1]])
      for (j in 1:length(dat_selectors[[file_dim]])) {
        sv <- selector_vector <- dat_selectors[[file_dim]][[j]]
        if (!identical(first_class, class(sv)) ||
            !identical(first_length, length(sv))) {
          stop("All provided selectors for depending dimensions must ",
               "be vectors of the same length and of the same class.")
        }
        if (is.character(sv) && !((length(sv) == 1) && (sv[1] %in% c('all', 'first', 'last')))) {
          dat_selectors[[file_dim]][[j]] <- selector_checker(selectors = sv,
                                                             return_indices = FALSE)
        } else if (!(is.numeric(sv) || 
                     (is.character(sv) && (length(sv) == 1) && (sv %in% c('all', 'first', 'last'))) || 
                     (is.list(sv) && (length(sv) == 2) && (all(sapply(sv, is.character)) || 
                                                           all(sapply(sv, is.numeric)))))) {
          stop("All explicitly provided selectors for file dimensions must be character strings.")
        } 
      }
      sv <- dat_selectors[[file_dim]][[1]]
      if (is.character(sv) && !((length(sv) == 1) && (sv[1] %in% c('all', 'first', 'last')))) {
        replace_values[[file_dim]] <- dat_selectors[[file_dim]][[1]][1]
      }
    }
#print("C")
    # Now we know which dimensions whose selectors are provided non-explicitly.
    undefined_file_dims <- file_dims[which(sapply(replace_values, is.null))]
    defined_file_dims <- file_dims[which(!(file_dims %in% undefined_file_dims))]
    # Quickly check if the depending dimensions are provided properly.
    for (file_dim in file_dims) {
      if (file_dim %in% names(depending_file_dims)) {
        ## TODO: Detect multi-dependancies and forbid.
        if (all(c(file_dim, depending_file_dims[[file_dim]]) %in% defined_file_dims)) {
          if (length(dat_selectors[[file_dim]]) != length(dat_selectors[[depending_file_dims[[file_dim]]]][[1]])) {
            stop(paste0("If providing selectors for the depending ",
                        "dimension '", file_dim, "', a ",
                        "vector of selectors must be provided for ",
                        "each selector of the dimension it depends on, '",
                        depending_file_dims[[file_dim]], "'."))
          } else if (!all(names(dat_selectors[[file_dim]]) == dat_selectors[[depending_file_dims[[file_dim]]]][[1]])) {
            stop(paste0("If providing selectors for the depending ",
                        "dimension '", file_dim, "', the name of the ",
                        "provided vectors of selectors must match ",
                        "exactly the selectors of the dimension it ",
                        "depends on, '", depending_file_dims[[file_dim]], "'."))
          }
        }
      }
    }
    # Find the possible values for the selectors that are provided as
    # indices. If the requested file is on server, impossible operation.
    if (length(grep("^http", dat[[i]][['path']])) > 0) {
      if (length(undefined_file_dims) > 0) {
        stop(paste0("All selectors for the file dimensions must be ",
                    "character strings if requesting data to a remote ",
                    "server. Found invalid selectors for the file dimensions ",
                    paste(paste0("'", undefined_file_dims, "'"), collapse = ', '), "."))
      }
      dataset_has_files[i] <- TRUE
    } else {
      # Iterate over the known dimensions to find the first existing file.
      # The path to the first existing file will be used to find the 
      # values for the non explicitly defined selectors.
      first_file <- NULL
      first_file_selectors <- NULL
      if (length(undefined_file_dims) > 0) {
        replace_values[undefined_file_dims] <- '*'
      }
      ## TODO: What if length of defined_file_dims is 0? code might crash (in practice it worked for an example case)
      files_to_check <- sapply(dat_selectors[defined_file_dims], function(x) length(x[[1]]))
      sub_array_of_files_to_check <- array(1:prod(files_to_check), dim = files_to_check)
      j <- 1
#print("D")
      while (j <= prod(files_to_check) && is.null(first_file)) {
        selector_indices <- which(sub_array_of_files_to_check == j, arr.ind = TRUE)[1, ]
        selectors <- sapply(1:length(defined_file_dims), 
          function (x) {
            vector_to_pick <- 1
            if (defined_file_dims[x] %in% names(depending_file_dims)) {
              vector_to_pick <- selector_indices[which(defined_file_dims == depending_file_dims[[defined_file_dims[x]]])]
            }
            dat_selectors[defined_file_dims][[x]][[vector_to_pick]][selector_indices[x]]
          })
        replace_values[defined_file_dims] <- selectors
        file_path <- .ConfigReplaceVariablesInString(dat[[i]][['path']], replace_values)
        file_path <- Sys.glob(file_path)
        if (length(file_path) > 0) {
          first_file <- file_path[1]
          first_file_selectors <- selectors
        }
        j <- j + 1
      }
#print("E")
      # Start looking for values for the non-explicitly defined selectors.
      if (is.null(first_file)) {
        .warning(paste0("No found files for the datset '", dat[[i]][['name']], 
                        "'. Provide existing selectors for the file dimensions ",
                        " or check and correct its path pattern: ", dat[[i]][['path']]))
      } else {
        dataset_has_files[i] <- TRUE
        ## TODO: Improve message here if no variable found:
        if (length(undefined_file_dims) > 0) {
          # Looking for the first values, parsed from first_file.
          first_values <- vector('list', length = length(undefined_file_dims))
          names(first_values) <- undefined_file_dims
          found_values <- 0
          stop <- FALSE
          try_dim <- 1
          last_success <- 1
#print("F")
          while ((found_values < length(undefined_file_dims)) && !stop) {
            u_file_dim <- undefined_file_dims[try_dim]
            if (is.null(first_values[[u_file_dim]])) {
              path_with_globs_and_tag <- .ConfigReplaceVariablesInString(dat[[i]][['path']], 
                                           replace_values[-which(file_dims == u_file_dim)],
                                           allow_undefined_key_vars = TRUE)
              found_value <- .FindTagValue(path_with_globs_and_tag, 
                                           first_file, u_file_dim)
              if (!is.null(found_value)) {
                found_values <- found_values + 1
                last_success <- try_dim
                first_values[[u_file_dim]] <- found_value
                replace_values[[u_file_dim]] <- found_value
              }
            }
            try_dim <- (try_dim %% length(undefined_file_dims)) + 1
            if (try_dim == last_success) {
              stop <- TRUE
            }
          }
#print("G")
          if (found_values < length(undefined_file_dims)) {
            stop(paste0("Path pattern of dataset '", dat[[i]][['name']], 
                        "' is too complex. Could not automatically ",
                        "detect values for all non-explicitly defined ",
                        "indices. Check its pattern: ", dat[[i]][['path']]))
          }
          ## TODO: Replace ReplaceGlobExpressions by looped call to FindTagValue? As done above
          ##       Maybe it can solve more cases actually. I got warnings in ReplGlobExp with a typical
          ##       cmor case, requesting all members and chunks for fixed var and sdate. Not fixing
          ##       sdate raised 'too complex' error.
          # Replace shell globs in path pattern and keep the file_dims as tags
          dat[[i]][['path']] <- .ReplaceGlobExpressions(dat[[i]][['path']], first_file, replace_values, 
                                                        file_dims, dat[[i]][['name']], FALSE)
          # Now time to look for the available values for the non 
          # explicitly defined selectors for the file dimensions.
#print("H")
          # Check first the ones that do not depend on others.
          ufd <- c(undefined_file_dims[which(!(undefined_file_dims %in% names(depending_file_dims)))],
                   undefined_file_dims[which(undefined_file_dims %in% names(depending_file_dims))])
                  
          for (u_file_dim in ufd) {
            replace_values[undefined_file_dims] <- first_values
            replace_values[[u_file_dim]] <- '*'
            depended_dim <- NULL
            depended_dim_values <- NA
            selectors <- dat_selectors[[u_file_dim]][[1]]
            if (u_file_dim %in% names(depending_file_dims)) {
              depended_dim <- depending_file_dims[[u_file_dim]]
              depended_dim_values <- dat_selectors[[depended_dim]][[1]]
              dat_selectors[[u_file_dim]] <- vector('list', length = length(depended_dim_values))
              names(dat_selectors[[u_file_dim]]) <- depended_dim_values
            } else {
              dat_selectors[[u_file_dim]] <- list()
            }
            if (u_file_dim %in% unlist(depending_file_dims)) {
              depending_dims <- names(depending_file_dims)[which(sapply(depending_file_dims, function(x) u_file_dim %in% x))]
              replace_values[depending_dims] <- rep('*', length(depending_dims))
            }
            for (j in 1:length(depended_dim_values)) {
              parsed_values <- c()
              if (!is.null(depended_dim)) {
                replace_values[[depended_dim]] <- depended_dim_values[j]
              }
              path_with_globs <- .ConfigReplaceVariablesInString(dat[[i]][['path']], replace_values)
              found_files <- Sys.glob(path_with_globs)
              ## TODO: Enhance this error message, or change by warning.
              ##       Raises if a wrong sdate is specified, for example.
              if (length(found_files) == 0) {
                .warning(paste0("Could not find files for any '", u_file_dim, 
                                "' for '", depended_dim, "' = '", 
                                depended_dim_values[j], "'."))
                dat_selectors[[u_file_dim]][[j]] <- NA
              } else {
                for (found_file in found_files) {
                  path_with_globs_and_tag <- .ConfigReplaceVariablesInString(dat[[i]][['path']],
                                               replace_values[-which(file_dims == u_file_dim)],
                                               allow_undefined_key_vars = TRUE)
                  parsed_values <- c(parsed_values, 
                                     .FindTagValue(path_with_globs_and_tag, found_file, 
                                                   u_file_dim))
                }
                dat_selectors[[u_file_dim]][[j]] <- selector_checker(selectors = selectors,
                                                                     var = unique(parsed_values),
                                                                     return_indices = FALSE)
              }
            }
          }
#print("I")
        } else {
          dat[[i]][['path']] <- .ReplaceGlobExpressions(dat[[i]][['path']], first_file, replace_values, 
                                                        defined_file_dims, dat[[i]][['name']], FALSE)
        }
      }
    }
    # Now fetch for the first available file
    if (dataset_has_files[i]) {
      known_dims <- file_dims
    } else {
      known_dims <- defined_file_dims
    }
    replace_values <- vector('list', length = length(known_dims))
    names(replace_values) <- known_dims
    files_to_load <- sapply(dat_selectors[known_dims], function(x) length(x[[1]]))
    files_to_load[found_pattern_dim] <- 1
    sub_array_of_files_to_load <- array(1:prod(files_to_load), 
                                        dim = files_to_load,
                                        dimnames = known_dims)
    sub_array_of_not_found_files <- array(!dataset_has_files[i], 
                                          dim = files_to_load,
                                          dimnames = known_dims)
    j <- 1
    while (j <= prod(files_to_load)) {
      selector_indices <- which(sub_array_of_files_to_load == j, arr.ind = TRUE)[1, ]
      names(selector_indices) <- known_dims
      selectors <- sapply(1:length(known_dims), 
        function (x) {
          vector_to_pick <- 1
          if (known_dims[x] %in% names(depending_file_dims)) {
            vector_to_pick <- selector_indices[which(known_dims == depending_file_dims[[known_dims[x]]])]
          }
          dat_selectors[known_dims][[x]][[vector_to_pick]][selector_indices[x]]
        })
      names(selectors) <- known_dims
      replace_values[known_dims] <- selectors
      if (!dataset_has_files[i]) {
        if (any(is.na(selectors))) {
          replace_values <- replace_values[-which(names(replace_values) %in% names(selectors[which(is.na(selectors))]))]
        }
        file_path <- .ConfigReplaceVariablesInString(dat[[i]][['path']], replace_values, TRUE)
        sub_array_of_files_to_load[j] <- file_path        
      } else {
        if (any(is.na(selectors))) {
          replace_values <- replace_values[-which(names(replace_values) %in% names(selectors[which(is.na(selectors))]))]
          file_path <- .ConfigReplaceVariablesInString(dat[[i]][['path']], replace_values, TRUE)
          sub_array_of_files_to_load[j] <- file_path
          sub_array_of_not_found_files[j] <- TRUE
        } else {
          file_path <- .ConfigReplaceVariablesInString(dat[[i]][['path']], replace_values)
          sub_array_of_files_to_load[j] <- file_path
          if (is.null(indices_of_first_files_with_data[[i]])) {
            if (!(length(grep("^http", file_path)) > 0)) {
              if (!file.exists(file_path)) {
                file_path <- NULL
              }
            }
            if (!is.null(file_path)) {
              test_file <- NULL
              ## TODO: suppress error messages
              test_file <- file_opener(file_path)
              if (!is.null(test_file)) {
                selector_indices[which(known_dims == found_pattern_dim)] <- i
                indices_of_first_files_with_data[[i]] <- selector_indices
                selectors_of_first_files_with_data[[i]] <- selectors
                file_closer(test_file)
              }
            }
          }
        }
      }
      j <- j + 1
    }
    # Extend array as needed progressively
    if (is.null(array_of_files_to_load)) {
        array_of_files_to_load <- sub_array_of_files_to_load
        array_of_not_found_files <- sub_array_of_not_found_files
    } else {
      array_of_files_to_load <- .MergeArrays(array_of_files_to_load, sub_array_of_files_to_load,
                                             along = found_pattern_dim)
      ## TODO: file_dims, and variables like that.. are still ok now? I don't think so
      array_of_not_found_files <- .MergeArrays(array_of_not_found_files, sub_array_of_not_found_files,
                                               along = found_pattern_dim)
    }    
    dat[[i]][['selectors']] <- dat_selectors
  }
  if (all(sapply(indices_of_first_files_with_data, is.null))) {
    stop("No data files found for any of the specified datasets.")
  }
#print("J")
  ## TODO: To be run in parallel (local multi-core)
  # Now time to work out the inner file dimensions.
  # First pick the requested variables.
  dims_to_iterate <- NULL
  for (return_var in names(return_vars)) {
    dims_to_iterate <- unique(c(dims_to_iterate, return_vars[[return_var]]))
  }
  if (found_pattern_dim %in% dims_to_iterate) {
    dims_to_iterate <- dims_to_iterate[-which(dims_to_iterate == found_pattern_dim)]
  }
  common_return_vars <- NULL
  common_first_found_file <- NULL
  common_return_vars_pos <- NULL
  if (length(return_vars) > 0) {
    common_return_vars_pos <- which(sapply(return_vars, function(x) !(found_pattern_dim %in% x)))
  }
  if (length(common_return_vars_pos) > 0) {
    common_return_vars <- return_vars[common_return_vars_pos]
    return_vars <- return_vars[-common_return_vars_pos]
    common_first_found_file <- rep(FALSE, length(which(sapply(common_return_vars, length) == 0)))
    names(common_first_found_file) <- names(common_return_vars[which(sapply(common_return_vars, length) == 0)])
  }
  return_vars <- lapply(return_vars, 
    function(x) {
      if (found_pattern_dim %in% x) {
        x[-which(x == found_pattern_dim)]
      } else {
        x
      }
    })
  if (length(common_return_vars) > 0) {
    picked_common_vars <- vector('list', length = length(common_return_vars))
    names(picked_common_vars) <- names(common_return_vars)
  } else {
    picked_common_vars <- NULL
  }
  picked_vars <- vector('list', length = length(dat))
  names(picked_vars) <- dat_names
  for (i in 1:length(dat)) {
    if (dataset_has_files[i]) {
      if (length(return_vars) > 0) {
        picked_vars[[i]] <- vector('list', length = length(return_vars))
        names(picked_vars[[i]]) <- names(return_vars)
      }
      indices_of_first_file <- as.list(indices_of_first_files_with_data[[i]])
      array_dims <- sapply(dat[[i]][['selectors']][found_file_dims[[i]]], function(x) length(x[[1]]))
      names(array_dims) <- found_file_dims[[i]]
      if (length(dims_to_iterate) > 0) {
        indices_of_first_file[dims_to_iterate] <- lapply(array_dims[dims_to_iterate], function(x) 1:x)
      }
      array_of_var_files <- do.call('[', c(list(x = array_of_files_to_load), indices_of_first_file, list(drop = FALSE)))
      array_of_var_indices <- array(1:length(array_of_var_files), dim = array_dims)
      array_of_not_found_var_files <- do.call('[', c(list(x = array_of_not_found_files), indices_of_first_file, list(drop = FALSE)))
      previous_indices <- rep(-1, length(array_dims))
      names(previous_indices) <- names(array_dims)
      first_found_file <- NULL
      if (length(return_vars) > 0) {
        first_found_file <- rep(FALSE, length(which(sapply(return_vars, length) == 0)))
        names(first_found_file) <- names(return_vars[which(sapply(return_vars, length) == 0)])
      }
      for (j in 1:length(array_of_var_files)) {
        current_indices <- which(array_of_var_indices == j, arr.ind = TRUE)[1, ]
        names(current_indices) <- names(array_dims)
        if (!is.na(array_of_var_files[j]) && !array_of_not_found_var_files[j]) {
          changed_dims <- which(current_indices != previous_indices)
          vars_to_read <- NULL
          if (length(return_vars) > 0) {
            vars_to_read <- names(return_vars)[sapply(return_vars, function(x) any(names(changed_dims) %in% x))]
          }
          if (!is.null(first_found_file)) {
            if (any(!first_found_file)) {
              vars_to_read <- c(vars_to_read, names(first_found_file[which(!first_found_file)]))
            }
          }
          if ((i == 1) && (length(common_return_vars) > 0)) {
            vars_to_read <- c(vars_to_read, names(common_return_vars)[sapply(common_return_vars, function(x) any(names(changed_dims) %in% x))])
          }
          if (!is.null(common_first_found_file)) {
            if (any(!common_first_found_file)) {
              vars_to_read <- c(vars_to_read, names(common_first_found_file[which(!common_first_found_file)]))
            }
          }
          file_object <- file_opener(array_of_var_files[j])
          if (is.null(file_object)) {
            stop("Unexpected error. Could not find file ", array_of_var_files[j])
          }
          for (var_to_read in vars_to_read) {
            var_name_to_reader <- var_to_read
            names(var_name_to_reader) <- 'var'
            var_dims <- file_dim_reader(NULL, file_object, var_name_to_reader, 
                                        NULL, file_opener, file_closer)
            if (!is.null(var_dims)) {
              var_file_dims <- NULL
              if (var_to_read %in% names(common_return_vars)) {
                var_to_check <- common_return_vars[[var_to_read]]
              } else {
                var_to_check <- return_vars[[var_to_read]]
              }
              if (any(names(dim(array_of_files_to_load)) %in% var_to_check)) {
                var_file_dims <- dim(array_of_files_to_load)[which(names(dim(array_of_files_to_load)) %in% 
                                                                   var_to_check)]
              }
              if (((var_to_read %in% names(common_return_vars)) && 
                   is.null(picked_common_vars[[var_to_read]])) ||
                  ((var_to_read %in% names(return_vars)) && 
                   is.null(picked_vars[[i]][[var_to_read]]))) {
                if (any(names(var_file_dims) %in% names(var_dims))) {
                  stop("Found a requested var in 'return_var' requested for a ",
                       "file dimension which also appears in the dimensions of ",
                       "the variable inside the file.\n", array_of_var_files[j])
                }
                special_types <- list('POSIXct' = as.POSIXct, 'POSIXlt' = as.POSIXlt, 
                                      'Date' = as.Date)
                first_sample <- file_var_reader(NULL, file_object, var_to_read, file_opener, file_closer)
                if (any(class(first_sample) %in% names(special_types))) {
                  array_size <- prod(c(var_file_dims, var_dims))
                  new_array <- rep(special_types[[class(first_sample)[1]]](NA), array_size)
                  dim(new_array) <- c(var_file_dims, var_dims)
                } else {
                  new_array <- array(dim = c(var_file_dims, var_dims))
                }
                if (var_to_read %in% names(common_return_vars)) {
                  picked_common_vars[[var_to_read]] <- new_array
                } else {
                  picked_vars[[i]][[var_to_read]] <- new_array
                }
              } else {
                if (var_to_read %in% names(common_return_vars)) {
                  array_var_dims <- dim(picked_common_vars[[var_to_read]])
                } else {
                  array_var_dims <- dim(picked_vars[[i]][[var_to_read]])
                }
                if (any(names(array_var_dims) %in% names(var_file_dims))) {
                  array_var_dims <- array_var_dims[-which(names(array_var_dims) %in% names(var_file_dims))]
                }
                if (names(array_var_dims) != names(var_dims)) {
                  stop("Error while reading the variable '", var_to_read, "' from ",
                       "the file. Dimensions do not match.\nExpected ", 
                       paste(paste0("'", names(array_var_dims), "'"), 
                             collapse = ', '), " but found ",
                       paste(paste0("'", names(var_dims), "'"), 
                             collapse = ', '), ".\n", array_of_var_files[j])
                }
                if (any(var_dims > array_var_dims)) {
                  stop("Error while reading the variable '", var_to_read, "' from ",
                       "the file. Found size (", paste(var_dims, collapse = ' x '), 
                       ") is greater than expected maximum size (", 
                       array_var_dims, ").")
                }
              }
              var_store_indices <- c(as.list(current_indices[names(var_file_dims)]), lapply(var_dims, function(x) 1:x))
              if (var_to_read %in% names(common_return_vars)) {
                 picked_common_vars[[var_to_read]] <- do.call('[<-', 
                   c(list(x = picked_common_vars[[var_to_read]]), 
                     var_store_indices, 
                     list(value = file_var_reader(NULL, file_object, var_to_read, file_opener, file_closer))))
              } else {
                 picked_vars[[i]][[var_to_read]] <- do.call('[<-', 
                   c(list(x = picked_vars[[i]][[var_to_read]]), 
                     var_store_indices, 
                     list(value = file_var_reader(NULL, file_object, var_to_read, file_opener, file_closer))))
              }
              if (var_to_read %in% names(first_found_file)) {
                first_found_file[var_to_read] <- TRUE
              }
              if (var_to_read %in% names(common_first_found_file)) {
                common_first_found_file[var_to_read] <- TRUE
              }
            } else {
              stop("Could not find variable '", var_to_read, 
                   "' in the file ", array_of_var_files[j])
            }
          }
          file_closer(file_object)  
        }
        previous_indices <- current_indices
      }
    }
  }
  # Once we have the variable values, we can work out the indices
  # for the implicitly defined selectors.
  for (i in 1:length(dat)) {
    if (dataset_has_files[i]) {
      # Put all selectors in a list of a single list/vector of selectors. 
      # The dimensions that go across files will later be extended to have 
      # lists of lists/vectors of selectors.
      for (inner_dim in expected_inner_dims[[i]]) {
        if (!is.list(dat[[i]][['selectors']][[inner_dim]]) || 
            (is.list(dat[[i]][['selectors']][[inner_dim]]) && 
             length(dat[[i]][['selectors']][[inner_dim]]) == 2 &&
             is.null(names(dat[[i]][['selectors']][[inner_dim]])))) {
          dat[[i]][['selectors']][[inner_dim]] <- list(dat[[i]][['selectors']][[inner_dim]])
        }
      }
      indices <- indices_of_first_files_with_data[[i]] 
      if (!is.null(indices)) {
        for (inner_dim in expected_inner_dims[[i]]) {
          picked_var <- NULL
          var_with_selectors <- var_params[[inner_dim]]
          # If the selectors come with an associated variable
          if (!is.null(var_with_selectors)) {
            if (var_with_selectors %in% names(picked_vars[[i]])) {
              picked_var <- picked_vars[[i]][[var_with_selectors]]
            } else if (var_with_selectors %in% names(general_picked_vars)) {
              picked_var <- general_picked_vars[[var_with_selectors]]
            }
            var_dims <- dim(picked_var)
            var_file_dims <- 1
            if (any(names(var_dims) %in% found_file_dims[[i]])) {
              var_file_dims <- var_dims[which(names(var_dims) %in% found_file_dims[[i]])]
              var_dims <- var_dims[-which(names(var_dims) %in% found_file_dims[[i]])]
            }
            if (length(var_dims) > 1) {
              stop("Specified a '", inner_dim, "_var' for the dimension '", 
                   inner_dim, "', which goes across files (across '", file_dim, 
                   "'). The specified variable, '", picked_var, "', has more ",
                   "than one dimension and can not be used as selector variable. ",
                   "Select another variable or fix it in the files.")
            }
            dims_to_iterate <- var_file_dims
            file_dim_across <- NULL
            if (inner_dim %in% unlist(inner_dims_across_files)) {
              file_dim_across <- names(inner_dims_across_files)[which(sapply(inner_dims_across_files, function(x) inner_dim %in% x))[1]]
              if (!(file_dim %in% names(var_file_dims))) {
                stop("Specified a '", inner_dim, "_var' for the dimension '", 
                     inner_dim, "', which goes across files (across '", file_dim, 
                     "'). The specified variable, '", picked_var, "', must be ",
                     "requested for the dimension it goes across ('", file_dim, "').")
              }
              dims_to_iterate <- dims_to_iterate[-which(names(dims_to_iterate) == file_dim_across)]
            }
            array_of_var_dims_to_iterate <- array(1:prod(dims_to_iterate), dim = dims_to_iterate)
            found_indices <- vector('list', length = prod(var_file_dims))
            dim(found_indices) <- var_file_dims
            for (j in 1:prod(dims_to_iterate)) {
              var_file_indices <- which(array_of_var_dims_to_iterate == j, arr.ind = TRUE)[1, ]
              var_indices_to_take <- as.list(replicate(length(dim(picked_var)), TRUE))
              names(var_indices_to_take) <- names(dim(picked_var))
              var_indices_to_take[names(dims_to_iterate)] <- var_file_indices
              if (is.null(file_dim_across)) {
                checked_indices <- selector_checker(dat[[i]][['selectors']][[inner_dim]][[1]], 
                                                    var = do.call('[', c(list(x = picked_var), var_indices_to_take,
                                                                         list(drop = FALSE))))
                if (length(checked_indices) > 0) {
                  found_indices[[j]] <- checked_indices
                }
              } else {
                checked_indices <- selector_checker(dat[[i]][['selectors']][[inner_dim]][[1]], 
                                                    var = do.call('[', c(list(x = picked_var), 
                                                                         var_indices_to_take,
                                                                         list(drop = FALSE))))
                if (length(checked_indices) > 0) {
                  found_indices_pos <- var_file_dims
                  found_indices_pos[names(dims_to_iterate)] <- var_file_indices
                  chunk_amount <- dim(picked_var)[file_dim_across]
                  chunk_length <- prod(dim(picked_var)[names(var_dims)])
                  for (k in 1:chunk_amount) {
                    found_indices_pos[file_dim_across] <- k
                    final_indices <- which((seq(1, chunk_amount * chunk_length, chunk_amount) + (k - 1)) %in%
                                            checked_indices)
                    if (length(final_indices) > 0) {
                      final_indices <- ((final_indices - 1) %% chunk_length) + 1
                      found_indices <- do.call('[[<-', c(list(found_indices), as.list(found_indices_pos),
                                                         list(value = final_indices)))
                    }
                  }
                }
              }
            }
#flag empty chunks
          # If the selectors come alone (without any associated variable)
          } else {
            if (is.numeric(dat[[i]][['selectors']][[inner_dim]][[1]]) ||
                (is.character(dat[[i]][['selectors']][[inner_dim]][[1]]) &&
                 length(dat[[i]][['selectors']][[inner_dim]][[1]]) == 1 &&
                 dat[[i]][['selectors']][[inner_dim]][[1]] %in% c('all', 'first', 'last') &&
                 !is.null(file_dim_reader))) {
              ## TODO HERE::
              #- indices_of_first_files_with_data may change, because array is now extended
              file_path <- do.call("[", c(list(array_of_files_to_load), as.list(indices_of_first_files_with_data[[i]])))
              data_dims <- file_dim_reader(file_path, NULL, selectors_of_first_files_with_data[[i]],
                                           lapply(dat[[i]][['selectors']][expected_inner_dims[[i]]], '[[', 1),
                                           file_opener, file_closer)
              if (inner_dim %in% names(data_dims)) {
                file_dim <- NULL
                if (inner_dim %in% unlist(inner_dims_across_files)) {
                  file_dim <- names(inner_dims_across_files)[which(sapply(inner_dims_across_files, function(x) inner_dim %in% x))[1]]
                  chunk_amount <- length(dat[[i]][['selectors']][[file_dim]][[1]])
                  names(chunk_amount) <- file_dim
                } else {
                  chunk_amount <- 1
                }
                if ((length(dat[[i]][['selectors']][[inner_dim]][[1]]) == 1) &&
                    is.character(dat[[i]][['selectors']][[inner_dim]][[1]])) {
                  found_indices <- vector('list', length = chunk_amount)
                  dim(found_indices) <- c(chunk_amount)
                  if (dat[[i]][['selectors']][[inner_dim]][[1]] == 'all') {
                    found_indices[] <- replicate(chunk_amount, list(1:(data_dims[inner_dim])))
                  } else if (dat[[i]][['selectors']][[inner_dim]][[1]] == 'first') {
                    found_indices[[1]] <- 1
                  } else if (dat[[i]][['selectors']][[inner_dim]][[1]] == 'last') {
                    found_indices[[chunk_amount]] <- data_dims[inner_dim]
                  }
                } else {
                  selector_array <- dat[[i]][['selectors']][[inner_dim]][[1]]
                  if (is.null(dim(selector_array))) {
                    dim(selector_array) <- length(selector_array)
                  }
                  if (is.null(names(dim(selector_array)))) {
                    if (length(dim(selector_array)) == 1) {
                      names(dim(selector_array)) <- inner_dim
                    } else {
                      stop("Provided a selector arrays must be provided with dimension ",
                           "names. Found an array of selectors without dimension names ",
                           "for the dimension '", inner_dim, "'.")
                    } 
                  }
                  selector_file_dims <- 1
                  if (any(found_file_dims[[i]] %in% names(dim(selector_array)))) {
                    selector_file_dims <- dim(selector_array)[which(names(dim(selector_array)) %in% found_file_dims[[i]])]
                  }
                  if (is.null(names(selector_file_dims))) {
                    found_indices_dims <- 1
                  } else {
                    found_indices_dim_names <- names(selector_file_dims)
                    if (!is.null(file_dim)) {
                      found_indices_dim_names <- c(found_indices_dim_names, file_dim)
                    }
                    found_indices_dim_names <- found_file_dims[[i]][which(found_file_dims[[i]] %in% found_indices_dim_names)]
                    found_indices_dims <- rep(NA, length(found_indices_dim_names))
                    names(found_indices_dims) <- found_indices_dim_names
                    found_indices_dims[names(selector_file_dims)] <- selector_file_dims
                    if (!is.null(file_dim)) {
                      found_indices_dims[file_dim] <- chunk_amount
                    }
                  }
                  found_indices <- vector('list', length = prod(found_indices_dims))
                  dim(found_indices) <- found_indices_dims
                  selector_file_dim_array <- array(1:prod(selector_file_dims), dim = selector_file_dims)
                  selector_store_position <- found_indices_dims
                  for (j in 1:prod(dim(selector_file_dim_array))) {
                    selector_indices_to_take <- which(selector_file_dim_array == j, arr.ind = TRUE)[1, ]
                    names(selector_indices_to_take) <- names(selector_file_dims)
                    selector_store_position[names(selector_indices_to_take)] <- selector_indices_to_take
                    if (!is.null(file_dim)) {
                      if (file_dim %in% names(selector_file_dims)) {
                        sub_array_of_found_indices <- Subset(selector_array, names(selector_indices_to_take),
                                                             as.list(selector_indices_to_take), drop = 'selected')
                        if (any(sub_array_of_found_indices > data_dims[inner_dim]) ||
                            any(sub_array_of_found_indices < 1)) {
                          stop("Provided indices out of range for dimension '", inner_dim, "' ", 
                               "for dataset '", dat[[i]][['name']], "' (accepted range: 1 to ", 
                               data_dims[inner_dim], ").")
                        }
                        found_indices <- do.call('[[<-', c(list(x = found_indices), as.list(selector_store_position),
                                                           list(value = sub_array_of_found_indices)))
                      } else {
                        sub_array_of_found_indices <- Subset(selector_array, names(selector_indices_to_take),
                                                             as.list(selector_indices_to_take), drop = 'selected')
                        if (inner_dim %in% names(dim(sub_array_of_found_indices))) {
                          if (any(sub_array_of_found_indices < 1) ||
                              any(sub_array_of_found_indices > data_dims[inner_dim] * chunk_amount)) {
                            stop("Provided indices out of range for dimension '", inner_dim, "' ", 
                                 "for dataset '", dat[[i]][['name']], "' (accepted range: 1 to ", 
                                 data_dims[inner_dim] * chunk_amount, ").")
                          }
                          inner_dim_pos_in_sub_array <- which(names(dim(sub_array_of_found_indices)) == inner_dim)
                          if (inner_dim_pos_in_sub_array != 1) {
                            new_sub_array_order <- (1:length(dim(sub_array_of_found_indices)))[-inner_dim_pos_in_sub_array]
                            new_sub_array_order <- c(inner_dim_pos_in_sub_array, new_sub_array_order)
                            sub_array_of_found_indices <- aperm(sub_array_of_found_indices, new_sub_array_order)
                          }
                          indices_chunk <- floor((sub_array_of_found_indices - 1) / data_dims[inner_dim]) + 1
                          transformed_indices <- ((sub_array_of_found_indices - 1) %% data_dims[inner_dim]) + 1
                          for (chunk in 1:chunk_amount) {
                            selector_store_position[file_dim] <- k
                            found_indices <- do.call('[[<-', c(list(x = found_indices), as.list(selector_store_position),
                                                               list(value = transformed_indices[which(indices_chunk == chunk)])))
                          }
                        } else {
                          stop("Provided array of indices for dimension '", inner_dim, "', ",
                               "which goes across the file dimension '", file_dim, "', but ",
                               "the provided array does not have the dimension '", inner_dim, 
                               "', which is mandatory.")
                        }
                      }
                    } else {
                      sub_array_of_found_indices <- Subset(selector_array, names(selector_indices_to_take),
                                                           as.list(selector_indices_to_take), drop = 'selected')
                      if (any(sub_array_of_found_indices > data_dims[inner_dim]) ||
                          any(sub_array_of_found_indices < 1)) {
                        stop("Provided indices out of range for dimension '", inner_dim, "' ", 
                             "for dataset '", dat[[i]][['name']], "' (accepted range: 1 to ", 
                             data_dims[inner_dim], ").")
                      }
                      found_indices <- do.call('[[<-', c(list(x = found_indices), as.list(selector_store_position),
                                                         list(value = sub_array_of_found_indices)))
                    }
                  }   
# flag empty cunks
                }
              } else {
                stop("Could not find the dimension '", inner_dim, "' in ",
                     "the file. Either change the dimension name in ",
                     "your request, adjust the parameter ",
                     "'dim_names_in_files' or fix the dimension name in ",
                     "the file.\n", file_path)
              }
            } else {
              stop(paste0("Can not translate the provided selectors for '", inner_dim, 
                          "' to numeric indices. Provide numeric indices and a ",
                          "'file_dim_reader' function, or a '", inner_dim, 
                          "_var' in order to calculate the indices."))
            }
          }
          if (!((inner_dim %in% c('var', 'variable')) && (is.character(dat[[i]][['selectors']][[inner_dim]][[1]])) &&
                !((length(dat[[i]][['selectors']][[inner_dim]][[1]]) == 1) && 
                  (dat[[i]][['selectors']][[inner_dim]][[1]] %in% c('all', 'first', 'last'))))) {
#            if (inner_dim %in% unlist(inner_dims_across_files)) {
#              ## TODO: The [1] after the which is in case more than one file dimensions
#              ##       have the inner_dim associated (in inner_dims_across_files), but 
#              ##       this should be forbidden above.
#              file_dim <- names(inner_dims_across_files)[which(sapply(inner_dims_across_files, function(x) inner_dim %in% x))[1]]
#              dat[[i]][['selectors']][[inner_dim]] <- vector('list', length = 1)
##                length = length(dat[[i]][['selectors']][[file_dim]][[1]]))
#              if (!(file_dim %in% names(depending_file_dims))) {
#                names(dat[[i]][['selectors']][[inner_dim]]) <- dat[[i]][['selectors']][[file_dim]][[1]]
#              } else {
#                names(dat[[i]][['selectors']][[inner_dim]]) <- paste0(file_dim, 1:length(dat[[i]][['selectors']][[file_dim]][[1]]))
#              }
#              taken_indices <- 0
#              chunks_to_remove <- NULL
#              remove_last_chunks <- FALSE
#              j <- 1
#              dat[[i]][['selectors']][[inner_dim]] <- lapply(dat[[i]][['selectors']][[inner_dim]], function(x) {
#                r <- found_indices[which((found_indices > taken_indices) & (found_indices <= taken_indices + picked_var_chunk_lengths[j]))]
#                r <- r - taken_indices
#                taken_indices <<- taken_indices + picked_var_chunk_lengths[j]
#                if (length(r) == 0) {
#                  chunks_to_remove <<- c(chunks_to_remove, j)
#                  if (j == length(dat[[i]][['selectors']][[inner_dim]])) {
#                    remove_last_chunks <<- TRUE
#                  }
#                }
#                j <<- j + 1
#                r
#              })
#              if (remove_last_chunks) {
#                # Get the last group of chunks to remove, and remove them. 
#                # E.g., from c(1, 2, 4, 5, 6, 8, 9) remove only 8 and 9
#                dist <- chunks_to_remove - c(chunks_to_remove[1] - 1, head(chunks_to_remove, length(chunks_to_remove) - 1))
#                first_chunk_to_remove <- tail(which(dist > 1), 1)
#                if (length(first_chunk_to_remove) > 0) {
#                  chunks_to_remove <- chunks_to_remove[first_chunk_to_remove:length(chunks_to_remove)]
#                }
#                dat[[i]][['selectors']][[inner_dim]] <- dat[[i]][['selectors']][[inner_dim]][-chunks_to_remove]
#                dat[[i]][['selectors']][[file_dim]][[1]] <- dat[[i]][['selectors']][[file_dim]][[1]][-chunks_to_remove]
#              }
#            } else {
#              dat[[i]][['selectors']][[inner_dim]][[1]] <- found_indices
#            }
            dat[[i]][['selectors']][[inner_dim]] <- found_indices
          }
        }
#print("L")
      }
    }
  }

#  # Remove the trailing chunks, if any.
#  for (file_dim in names(inner_dims_across_files)) {
#    dim_lengths <- sapply(dat, function(x) length(x[['selectors']][[file_dim]][[1]]))
#    if (max(dim_lengths) < dim(array_of_files_to_load)[file_dim]) {
#      array_of_files_to_load <- Subset(array_of_files_to_load, file_dim, 1:max(dim_lengths))
#      array_of_not_found_files <- Subset(array_of_not_found_files, file_dim, 1:max(dim_lengths))
#    }
#  }
  # Calculate the size of the final array.
  total_inner_dims <- NULL
  for (i in 1:length(dat)) {
    if (dataset_has_files[i]) {
      inner_dims <- expected_inner_dims[[i]]
      inner_dims <- sapply(inner_dims, function(x) max(sapply(dat[[i]][['selectors']][[x]], length)))
      names(inner_dims) <- expected_inner_dims[[i]]
      if (is.null(total_inner_dims)) {
        total_inner_dims <- inner_dims
      } else {
        new_dims <- .MergeArrayDims(total_inner_dims, inner_dims)
        total_inner_dims <- pmax(new_dims[[1]], new_dims[[2]])
      }
    }
  }
  new_dims <- .MergeArrayDims(dim(array_of_files_to_load), total_inner_dims)
  final_dims <- pmax(new_dims[[1]], new_dims[[2]])[dim_names]

  # TODO: try performance of storing all in cols instead of rows
  # Create the shared memory array, and a pointer to it, to be sent
  # to the work pieces.
  data_array <- big.matrix(nrow = prod(final_dims), ncol = 1)
  shared_matrix_pointer <- describe(data_array)
  if (is.null(num_procs)) {
    num_procs <- ceiling(availableCores() / 2)
  }
  # Build the work pieces, each with:
  # - file path
  # - total size (dims) of store array
  # - start position in store array
  # - file selectors (to provide extra info. useful e.g. to select variable)
  # - indices to take from file
  work_pieces <- list()
  for (i in 1:length(dat)) {
    if (dataset_has_files[i]) {
      selectors <- dat[[i]][['selectors']]
      file_dims <- found_file_dims[[i]]
      inner_dims <- expected_inner_dims[[i]]
      sub_array_dims <- final_dims[file_dims]
      sub_array_dims[found_pattern_dim] <- 1
      sub_array_of_files_to_load <- array(1:prod(sub_array_dims), 
                                          dim = sub_array_dims,
                                          dimnames = names(sub_array_dims))
      # Detect which of the dimensions of the dataset go across files.
      file_dim_across_files <- lapply(inner_dims, 
        function(x) {
          dim_across <- sapply(inner_dims_across_files, function(y) x %in% y)
          if (any(dim_across)) {
            names(inner_dims_across_files)[which(dim_across)[1]]
          } else {
            NULL
          }
        })
      names(file_dim_across_files) <- inner_dims
      j <- 1
#print("M")
      while (j <= prod(sub_array_dims)) {
        # Work out file path.
        file_to_load_sub_indices <- which(sub_array_of_files_to_load == j, arr.ind = TRUE)[1, ]
        names(file_to_load_sub_indices) <- names(sub_array_dims)
        file_to_load_sub_indices[found_pattern_dim] <- i
        big_dims <- rep(1, length(dim(array_of_files_to_load)))
        names(big_dims) <- names(dim(array_of_files_to_load))
        file_to_load_indices <- .MergeArrayDims(file_to_load_sub_indices, big_dims)[[1]]
        file_to_load <- do.call('[[', c(list(array_of_files_to_load), 
                                        as.list(file_to_load_indices)))
        not_found_file <- do.call('[[', c(list(array_of_not_found_files),
                                          as.list(file_to_load_indices)))
        if (!is.na(file_to_load) && !not_found_file) {
          # Work out indices to take
          inner_indices <- lapply(inner_dims, 
            function (x) {
              var_file_dims <- dim(selectors[inner_dims][[x]])
              if (is.null(names(var_file_dims))) {
                selectors[inner_dims][[x]][[1]]
              } else {
                which_indices <- var_file_dims
                which_indices <- file_to_load_sub_indices[which(names(sub_array_dims) %in% names(which_indices))] 
                do.call('[[', c(list(selectors[inner_dims][[x]]), as.list(which_indices)))
              }
            })
          names(inner_indices) <- inner_dims
          if (!any(sapply(inner_indices, length) == 0)) {
            work_piece <- list()
            work_piece[['inner_indices']] <- inner_indices
            work_piece[['file_indices_in_array_of_files']] <- file_to_load_indices
            work_piece[['file_path']] <- file_to_load
            work_piece[['store_dims']] <- final_dims
            # Work out store position
            store_position <- final_dims
            store_position[names(file_to_load_indices)] <- file_to_load_indices
            store_position[inner_dims] <- rep(1, length(inner_dims))
            work_piece[['store_position']] <- store_position
            # Work out file selectors
            file_selectors <- sapply(file_dims, 
              function (x) {
                vector_to_pick <- 1
                if (x %in% names(depending_file_dims)) {
                  vector_to_pick <- file_to_load_indices[depending_file_dims[[x]]]
                }
                selectors[file_dims][[x]][[vector_to_pick]][file_to_load_indices[x]]
              })
            work_piece[['file_selectors']] <- file_selectors
            work_pieces <- c(work_pieces, list(work_piece))
          }
        }
        j <- j + 1
      }
    }
  }
#print("N")

  # Calculate the progress %s that will be displayed and assign them to 
  # the appropriate work pieces.
  if (length(work_pieces) / num_procs >= 2 && !silent) {
    if (length(work_pieces) / num_procs < 10) {
      amount <- 100 / ceiling(length(work_pieces) / num_procs)
      reps <- ceiling(length(work_pieces) / num_procs)
    } else {
      amount <- 10
      reps <- 10
    }
    progress_steps <- rep(amount, reps)
    if (length(work_pieces) < (reps + 1)) {
      selected_pieces <- length(work_pieces)
      progress_steps <- c(sum(head(progress_steps, reps)),
                          tail(progress_steps, reps))
    } else {
      selected_pieces <- round(seq(1, length(work_pieces), 
                                   length.out = reps + 1))[-1]
    }
    progress_steps <- paste0(' + ', round(progress_steps, 2), '%')
    progress_message <- 'Progress: 0%'
  } else {
    progress_message <- ''
    selected_pieces <- NULL
  }
  piece_counter <- 1
  step_counter <- 1
  work_pieces <- lapply(work_pieces, 
    function (x) {
      if (piece_counter %in% selected_pieces) {
        wp <- c(x, list(progress_amount = progress_steps[step_counter]))
        step_counter <<- step_counter + 1
      } else {
        wp <- x
      }
      piece_counter <<- piece_counter + 1
      wp
    })
  if (!silent) {
    .message("Detected dimension sizes:")
    longest_dim_len <- max(sapply(names(final_dims), nchar))
    longest_size_len <- max(sapply(paste0(final_dims, ''), nchar))
    sapply(names(final_dims), 
      function(x) {
        message(paste0("*   ", paste(rep(' ', longest_dim_len - nchar(x)), collapse = ''), 
                       x, ": ", paste(rep(' ', longest_size_len - nchar(paste0(final_dims[x], ''))), collapse = ''), 
                       final_dims[x]))
      })
    .message(paste0("Will now proceed to read and process ", length(work_pieces), " data files:"))
    if (length(work_pieces) < 30) {
      lapply(work_pieces, function (x) .message(x[['file_path']], indent = 2))
    } else {
      .message("The list of files is long. You can check it after .Load() finishes in the output '$Files'.", indent = 2, exdent = 5)
    }
    bytes <- prod(c(final_dims, 8))
    dim_sizes <- paste(final_dims, collapse = ' x ')
    .message(paste("Total size of requested data:"))
    .message(paste(dim_sizes, " x 8 bytes =", 
                   format(structure(bytes, class = "object_size"), units = "auto")), 
                   indent = 2)
    .message("If the size of the requested data is close to or above the free shared RAM memory, R may crash.")
    .message("If the size of the requested data is close to or above the half of the free RAM memory, R may crash.")
  }

  # Build the cluster of processes that will do the work and dispatch work pieces.
  # The function .LoadDataFile is applied to each work piece. This function will
  # open the data file, regrid if needed, subset, apply the mask, 
  # compute and apply the weights if needed,
  # disable extreme values and store in the shared memory matrix.
#print("O")
  if (!silent) {
    .message("Loading... This may take several minutes...")
    if (progress_message != '') {
      .message(progress_message, appendLF = FALSE)
    }
  }
  if (num_procs == 1) {
    found_files <- lapply(work_pieces, .LoadDataFile, 
                          shared_matrix_pointer = shared_matrix_pointer,
                          file_opener = file_opener, file_closer = file_closer,
                          file_dim_reader = file_dim_reader,
                          file_data_reader = file_data_reader, 
                          silent = silent)
  } else {
    cluster <- makeCluster(num_procs, outfile = "")
    # Send the heavy work to the workers
    work_errors <- try({
      found_files <- clusterApplyLB(cluster, work_pieces, .LoadDataFile, 
                                    shared_matrix_pointer = shared_matrix_pointer,
                                    file_opener = file_opener, file_closer = file_closer,
                                    file_dim_reader = file_dim_reader,
                                    file_data_reader = file_data_reader, 
                                    silent = silent)
    })
    stopCluster(cluster)
  }

  if (!silent) {
    if (progress_message != '') {
      .message("\n", tag = '')
    }
  }
#print("P")
  data_array <- array(bigmemory::as.matrix(data_array), dim = final_dims)
  gc()

  failed_pieces <- work_pieces[which(unlist(found_files))]
  for (failed_piece in failed_pieces) {
    array_of_not_found_files <- do.call('[<-', 
      c(list(array_of_not_found_files), 
        as.list(failed_piece[['file_indices_in_array_of_files']]),
        list(value = TRUE)))
  }
  if (any(array_of_not_found_files)) {
    for (i in 1:prod(dim(array_of_files_to_load))) {
      if (is.na(array_of_not_found_files[i])) {
        array_of_files_to_load[i] <- NA
      } else {
        if (array_of_not_found_files[i]) {
          array_of_not_found_files[i] <- array_of_files_to_load[i]
          array_of_files_to_load[i] <- NA
        } else {
          array_of_not_found_files[i] <- NA
        }
      }
    }
  } else {
    array_of_not_found_files <- NULL
  }
  #Hard:
  #- Reordering lon and lat: after .Load
  #- Interpolating: in LoadDataFile
  #- New config file system ?
  #Medium:
  #- Define dimension names in file
  #- Define variable names in file
  #- Allow to match closest dates (does it work already?)
  #- Crop variable values
  #- Crop file array if possible
  #- Add option to collapse dimensions
  #- Sort out problem of repeated open() and missing close()
  #- Recover all Load options
  #- Wrapper -> new Load()
  #- Review TODOs
  #Bonus:
  #- Read metadata
  #- Enhance regexps
  ## TODO: varmin and varmax
  ## TODO: use the dim_checkers
  ## TODO: change all prints to warnings
  ## TODO: make all warnings only appear if silent = FALSE.

  list(Data = data_array, 
       Variables = c(list(common = picked_common_vars), picked_vars),
       Files = array_of_files_to_load, 
       NotFoundFiles = array_of_not_found_files)
}

# This function is the responsible for loading the data of each work
# piece.
.LoadDataFile <- function(work_piece, shared_matrix_pointer, 
                          shared_var_pointers,
                          file_opener, file_closer, file_dim_reader,
                          file_data_reader, silent = FALSE) {
  suppressPackageStartupMessages({library(ncdf4)})
  suppressPackageStartupMessages({library(bigmemory)})

  # Auxiliar function to convert array indices to lineal indices.
  # This function expects a numeric vector of single indices or a list of 
  # numeric vectors with multiple indices for each dimension, and a 
  # numeric vector of dimension sizes.
  .arrayIndices2VectorIndices <- function(indices, dims) {
    # Check indices
    if (!is.list(indices)) {
      if (is.numeric(indices)) {
        indices <- as.list(indices)
      } else {
        stop("Parameter 'indices' must be a numeric vector or a list of ",
             "numeric vectors.")
      }
    }
    sapply(indices, 
      function(x) {
        if (!is.numeric(x)) {
          stop("Parameter 'indices' must be a numeric vector or a list ",
               "of numeric vectors.")
        } else if (any(is.na(x) | is.nan(x) | is.infinite(x))) {
          stop("Parameter 'indices' must not contain NA/NaN/Inf values.")
        } else if (length(x) < 1) {
          stop("Parameter 'indices' must contain at least one index ",
               "for each dimension.")
        }
      })
  
    # Check dims
    if (!is.numeric(dims)) {
      stop("Parameter 'dims' must be a numeric vector.")
    } else if (any(is.na(dims) | is.nan(dims) | is.infinite(dims))) {
      stop("Parameter 'dims' must not contain NA/NaN/Inf values.")
    } else if (any(sapply(dims, length) != 1)) {
      stop("Parameter 'dims' must contain a single numeric element for ",
           "each dimension.")
    } else if (length(indices) != length(dims)) {
      stop("There must be as many dimension selectors in 'indices' as ",
           "dimensions in 'dims'.")
    }
  
    find_indices <- function(indices, dims) {
      if (max(indices[[1]]) > dims[1] || min(indices[[1]]) < 1) {
        stop("Provided indices out of range.")
      }
      if (length(dims) == 1) {
        indices[[1]]
      } else {
        found_indices <- find_indices(indices[-1], dims[-1])
        new_found_indices <- c()
        for (i in 1:length(indices[[1]])) {
          new_found_indices <- c(new_found_indices, (indices[[1]][i] - 1) * prod(dims[-1]) + found_indices)
        }
        new_found_indices
      }
    }
  
    indices <- rev(indices)
    dims <- rev(dims)
  
    find_indices(indices, dims)
  }

#print("1")
  data_array <- attach.big.matrix(shared_matrix_pointer)
  store_indices <- as.list(work_piece[['store_position']])
  inner_indices <- work_piece[['inner_indices']]
#print("2")
  store_indices[names(inner_indices)] <- lapply(inner_indices, 
    function(x) {
      if (is.numeric(x)) {
        ## TODO: Review carefully this line. Inner indices are all 
        ## aligned to the left-most positions. If dataset A has longitudes
        ## 1, 2, 3, 4 but dataset B has only longitudes 3 and 4, then
        ## they will be stored as follows:
        ## 1, 2, 3, 4
        ## 3, 4, NA, NA
        ##x - min(x) + 1
        1:length(x)
      } else {
        1:length(x)
      }
    })
  matrix_indices <- .arrayIndices2VectorIndices(store_indices, work_piece[['store_dims']])
#print("3")
  sub_array <- file_data_reader(work_piece[['file_path']], NULL, 
                                work_piece[['file_selectors']],
                                work_piece[['inner_indices']],
                                file_opener, file_closer, 
                                file_dim_reader)
#print("4")
  if (!is.null(sub_array)) {
    data_array[matrix_indices] <- sub_array
  }
  rm(data_array)
  gc()
  if (!is.null(work_piece[['progress_amount']]) && !silent) {
    message(work_piece[['progress_amount']], appendLF = FALSE)
  }

  is.null(sub_array)
}
