DownRToS2dv <- function(...) {
  library(abind)

  # This function assumes the two input objects have data for exactly the same start dates and forecast times.
  MergeS2dvEqualDats <- function(dat1, dat2) {
    if (!identical(dat1$lon, dat2$lon) || !identical(dat1$lat, dat2$lat)) {
      stop("Provided s2dverification data sets on different grids.")
    }
    if (!identical(dat1$Variable, dat2$Variable)) {
      stop("Provided s2dverification data sets of different variables.")
    }
    # Merge experimental data
    if (!is.null(dat1$mod)) {
      if (!is.null(dat2$mod)) {
        if (!(all(dim(dat1$mod)[c(3:6)] == dim(dat2$mod)[3:6]))) {
          stop("Provided s2dverification data sets with different number of sdates, ftimes, lats or lons.")
        }
        if (dim(dat1$mod)['member'] < dim(dat2$mod)['member']) {
          new_dims <- dim(dat1$mod)
          new_dims['member'] <- dim(dat2$mod)['member']
          dat1$mod <- abind(dat1$mod, array(dim = new_dims), along = 2)
          names(dim(dat1$mod)) <- names(new_dims)
          dat1$Datasets$exp <- lapply(dat1$Datasets$exp, 
                                      function(x) {
                                        x[['Members']] <- dat2$Datasets$exp[[1]]$Members
                                      })
        } else if (dim(dat1$mod)['member'] > dim(dat2$mod)['member']) {
          new_dims <- dim(dat2$mod)
          new_dims['member'] <- dim(dat1$mod)['member']
          dat2$mod <- abind(dat2$mod, array(dim = new_dims), along = 2)
          names(dim(dat2$mod)) <- names(new_dims)
          dat2$Datasets$exp <- lapply(dat2$Datasets$exp, 
                                      function(x) {
                                        x[['Members']] <- dat1$Datasets$exp[[1]]$Members
                                      })
        }
        new_dims <- dim(dat1$mod)
        dat1$mod <- abind(dat1$mod, dat2$mod, along = 1)
        names(dim(dat1$mod)) <- names(new_dims)
        attr(dat1$mod, 'dimensions') <- names(new_dims)
        dat1$Datasets$exp <- c(dat1$Datasets$exp, dat2$Datasets$exp)
      }
    } else {
      if (!is.null(dat2$mod)) {
        dat1$mod <- dat2$mod
        dat1$Datasets$exp <- dat2$Datasets$exp
      }
    }
        
    # Merge observational data
    if (!is.null(dat1$obs)) {
      if (!is.null(dat2$obs)) {
        if (!(all(dim(dat1$obs)[c(3:6)] == dim(dat2$obs)[3:6]))) {
          stop("Provided s2dverification data sets with different number of sdates, ftimes, lats or lons.")
        }
        if (dim(dat1$obs)['member'] < dim(dat2$obs)['member']) {
          new_dims <- dim(dat1$obs)
          new_dims['member'] <- dim(dat2$obs)['member']
          dat1$obs <- abind(dat1$obs, array(dim = new_dims), along = 2)
          names(dim(dat1$obs)) <- names(new_dims)
          dat1$Datasets$obs <- lapply(dat1$Datasets$obs, 
                                      function(x) {
                                        x[['Members']] <- dat2$Datasets$obs[[1]]$Members
                                      })
        } else if (dim(dat1$obs)['member'] > dim(dat2$obs)['member']) {
          new_dims <- dim(dat2$obs)
          new_dims['member'] <- dim(dat1$obs)['member']
          dat2$obs <- abind(dat2$obs, array(dim = new_dims), along = 2)
          names(dim(dat2$obs)) <- names(new_dims)
          dat2$Datasets$obs <- lapply(dat2$Datasets$obs, 
                                      function(x) {
                                        x[['Members']] <- dat1$Datasets$obs[[1]]$Members
                                      })
        }
        new_dims <- dim(dat1$obs)
        dat1$obs <- abind(dat1$obs, dat2$obs, along = 1)
        names(dim(dat1$obs)) <- names(new_dims)
        attr(dat1$obs, 'dimensions') <- names(new_dims)
        dat1$Datasets$obs <- c(dat1$Datasets$obs, dat2$Datasets$obs)
      }
    } else {
      if (!is.null(dat2$obs)) {
        dat1$obs <- dat2$obs
        dat1$Datasets$obs <- dat2$Datasets$obs
      }
    }
    dat1
  }

  DownRToS2dv1Dat <- function(downr_obj, sdates, ftimes = NULL) {
    if (length(attr(downr_obj$Data, 'dimensions')) < length(dim(downr_obj$Data))) {
      stop("Provided dowsncaleR objects with incorrect number of dimension names.")
    } else if (length(attr(downr_obj$Data, 'dimensions')) > length(dim(downr_obj$Data))) {
      attr(downr_obj$Data, 'dimensions') <- attr(downr_obj$Data, 'dimensions')[1:length(dim(downr_obj$Data)) + length(attr(downr_obj$Data, 'dimensions')) - length(dim(downr_obj$Data))] 
    }
    names(dim(downr_obj$Data)) <- attr(downr_obj$Data, 'dimensions')
    if ('InitializationDates' %in% names(downr_obj)) {
      is_exp <- TRUE
    } else {
      is_exp <- FALSE
    }
    obj_dates_start <- as.POSIXct(downr_obj$Dates$start)
    start_indices <- sapply(as.POSIXct(sdates), 
      function(x) {
        diff <- obj_dates_start - x
        candidates <- which(diff >= 0)
        if (length(candidates) < 1) {
          stop("Could not find data values with dates beyond one of the start dates.")
        }
        candidates[which.min(diff[candidates])[1]]
      })
    total_dates <- length(downr_obj$Dates$start)
    ftime_amounts <- c(start_indices, total_dates + 1)[2:(length(start_indices) + 1)] - start_indices
    largest_ftime <- max(ftime_amounts)
    if (!is.null(ftimes)) {
      if (ftimes < largest_ftime) {
        stop("Parameter 'ftimes' with a smaller number of forecast times than the provided in the input objects.")
      }
      largest_ftime <- ftimes
    }
    # Reordering data
    new_downr_obj <- downr_obj$Data
    new_dates <- downr_obj$Dates
    chunk_dims <- dim(downr_obj$Data)
    for (i in 1:length(ftime_amounts)) {
      ftime_amount <- ftime_amounts[i]
      if (ftime_amount < largest_ftime) {
        # Fill in data array and dates
        step_new_downr_obj <- NULL
        step_new_dates_s <- NULL
        step_new_dates_e <- NULL
        #if (largest_ftime * (i - 1) + ftime_amount > 0) {
        step_new_downr_obj <- Subset(new_downr_obj, 'time', 1:(largest_ftime * (i - 1) + ftime_amount))
        step_new_dates_s <- new_dates$start[1:(largest_ftime * (i - 1) + ftime_amount)]
        step_new_dates_e <- new_dates$end[1:(largest_ftime * (i - 1) + ftime_amount)]
        #}
        chunk_dims['time'] <- largest_ftime - ftime_amount
        step_new_downr_obj <- abind(step_new_downr_obj, array(dim = chunk_dims), along = which(names(chunk_dims) == 'time'))
        names(dim(step_new_downr_obj)) <- names(chunk_dims)
        step_new_dates_s <- c(step_new_dates_s, rep(NA, chunk_dims['time']))
        step_new_dates_e <- c(step_new_dates_e, rep(NA, chunk_dims['time']))
        if (dim(new_downr_obj)[which(names(chunk_dims) == 'time')] > (largest_ftime * (i - 1) + ftime_amount)) {
          step_new_downr_obj <- abind(step_new_downr_obj, 
                                 Subset(new_downr_obj, 'time', (largest_ftime * (i - 1) + ftime_amount + 1):
                                                          (dim(new_downr_obj)[which(names(chunk_dims) == 'time')])),
                                 along = which(names(chunk_dims) == 'time'))
          names(dim(step_new_downr_obj)) <- names(chunk_dims)
          step_new_dates_s <- c(step_new_dates_s, 
                                new_dates$start[(largest_ftime * (i - 1) + ftime_amount + 1):
                                                (dim(new_downr_obj)[which(names(chunk_dims) == 'time')])]
                               )
          step_new_dates_e <- c(step_new_dates_e, 
                                new_dates$end[(largest_ftime * (i - 1) + ftime_amount + 1):
                                              (dim(new_downr_obj)[which(names(chunk_dims) == 'time')])]
                               )
        }
        new_dates <- list(start = step_new_dates_s,
                          end = step_new_dates_e)
        new_downr_obj <- step_new_downr_obj
        rm(step_new_downr_obj)
        rm(step_new_dates_s)
        rm(step_new_dates_e)
        gc()
    #    new_dates$start <- c(head(new_dates$start, largest_ftime * (i - 1) + ftime_amount),
    #                         rep(NA, largest_ftime - ftime_amount),
    #                         new_dates$start[(largest_ftime * (i - 1) + ftime_amount + 1):
    #                                         (dim(new_data_obs)[which(names(chunk_dims_obs) == 'time')])]
    #                         )
    #    new_dates$end <- c(head(new_dates$end, largest_ftime * (i - 1) + ftime_amount),
    #                       rep(NA, largest_ftime - ftime_amount),
    #                       new_dates$end[(largest_ftime * (i - 1) + ftime_amount + 1):
    #                                       (dim(new_data_obs)[which(names(chunk_dims_obs) == 'time')])]
    ##                       tail(new_dates$end, length(new_dates$end) - (largest_ftime * (i - 1) + ftime_amount))
    #                       )
      }
    }

    # Defining data new dims
    new_dims <- dim(downr_obj$Data)
    time_dim <- which(names(new_dims) == 'time')
    before_dims <- NULL
    if (time_dim > 1) {
      before_dims <- new_dims[1:(time_dim - 1)]
    }
    after_dims <- NULL
    if (time_dim < length(new_dims)) {
      after_dims <- new_dims[(time_dim + 1):length(new_dims)]
    }
    new_dims <- c(before_dims, largest_ftime, length(sdates), after_dims)
    names(new_dims)[time_dim:(time_dim + 1)] <- c('ftime', 'sdate')
    if (!('member' %in% names(new_dims))) {
      new_dims <- c(c(member = 1), new_dims)
    }
    new_dims <- c(c(dataset = 1), new_dims)
    dim(new_downr_obj) <- new_dims
    perm <- 1:length(new_dims)
    perm[c(3, 4)] <- perm[c(4, 3)]
    new_downr_obj <- aperm(new_downr_obj, perm)
    names(dim(new_downr_obj)) <- names(new_dims)[perm]
    # Defining date new dims
    dim(new_dates$start) <- dim(new_downr_obj)[c('ftime', 'sdate')]
    new_dates$start <- t(new_dates$start)
    names(dim(new_dates$start)) <- c('sdate', 'ftime')
    dim(new_dates$end) <- dim(new_downr_obj)[c('ftime', 'sdate')]
    new_dates$end <- t(new_dates$end)
    names(dim(new_dates$end)) <- c('sdate', 'ftime')
    # Casting output
    result <- list()
    if (is_exp) {
      result$mod <- new_downr_obj
      result$obs <- NULL
      attr(result$mod, 'dataset') <- attr(downr_obj, 'dataset')
    } else {
      result$mod <- NULL
      result$obs <- new_downr_obj
      attr(result$obs, 'dataset') <- attr(downr_obj, 'dataset')
    }
    result$Dates <- new_dates
    result$lon <- downr_obj$xyCoords$x
    result$lat <- downr_obj$xyCoords$y
    result$InitializationDates <- downr_obj$InitializationDates
    result$Variable <- downr_obj$Variable
    result$Members <- downr_obj$Members
    #attributes(result) <- c(attributes(result), 
    #                      attributes(downr_obj)[-which(names(attributes(downr_obj_bk)) %in% c('names', 'dataset'))])
 
    result
  }

  args <- list(...)
  if (!all(sapply(args, 
                  function(x) {
                    all(c('Data', 'Variable', 'Dates', 'xyCoords') %in% names(x)) &&
                    all(c('dataset', 'source', 'URL') %in% names(attributes(x)))
                  }))) {
    stop("All provided objects in parameter '...' must be full dowsncaleR objects.")
  }

  is_exp <- sapply(args, function(x) 'InitializationDates' %in% names(x))
  exp_indices <- which(is_exp)
  obs_indices <- which(!is_exp)

  provided_data <- list(exp = list(), obs = list())

  # Populate provided_data with info on provided experimental data
  found_sdates <- NULL
  found_lons <- NULL
  found_lats <- NULL
  found_dates <- NULL
  for (exp in exp_indices) {
    dataset <- attr(args[[exp]], 'dataset')
    if (!(dataset %in% names(provided_data[['exp']]))) {
      provided_data[['exp']][[dataset]] <- list()
    }
    if (is.null(found_sdates)) {
      found_sdates <- args[[exp]][['InitializationDates']][[1]]
    } else if (!identical(args[[exp]][['InitializationDates']][[1]], found_sdates)) {
      stop("Provided downscaleR objects with different sets of start dates. ",
           "Objects with data for only one set of start dates must be provided.")
    }
    if (is.null(found_lons)) {
      found_lons <- args[[exp]]$xyCoords$x
    } else if (!identical(args[[exp]]$xyCoords$x, found_lons)) {
      stop("All provided downscaleR objects must be on the same grid.")
    }
    if (is.null(found_lats)) {
      found_lats <- args[[exp]]$xyCoords$y
    } else if (!identical(args[[exp]]$xyCoords$y, found_lats)) {
      stop("All provided downscaleR objects must be on the same grid.")
    }
    exp_members <- args[[exp]][['Members']]
    found_members <- lapply(provided_data[['exp']][[dataset]], '[[', 'members')
    matches <- sapply(found_members, identical, exp_members)
    if (any(matches)) {
      exp_ftimes <- args[[exp]]$Dates$start
      found_ftimes <- lapply(provided_data[['exp']][[dataset]][[which(matches)[1]]][['chunks']], '[[', 'ftimes')
      ftime_matches <- sapply(found_ftimes, identical, exp_ftimes)
      if (any(ftime_matches)) {
        stop("Provided twice the data for the same experiment, member and range of forecast times.")
      } else {
        provided_data[['exp']][[dataset]][[which(matches)[1]]][['chunks']] <- c(provided_data[['exp']][[dataset]][[which(matches)[1]]][['chunks']], 
                                                                                list(
                                                                                  list(ftimes = args[[exp]]$Dates$start,
                                                                                       exp_index = exp
                                                                                      )
                                                                                    )
                                                                               )
      }
    } else {
      provided_data[['exp']][[dataset]] <- c(provided_data[['exp']][[dataset]], 
                                             list(
                                               list(members = exp_members, 
                                                    chunks = list(
                                                               list(ftimes = args[[exp]]$Dates$start,
                                                                    exp_index = exp
                                                                   )
                                                                 )
                                                   )
                                                 )
                                            )
    }
  }

  # Populate provided_data with info on provided observational data.
  for (obs in obs_indices) {
    dataset <- attr(args[[obs]], 'dataset')
    if (!(dataset %in% names(provided_data[['obs']]))) {
      provided_data[['obs']][[dataset]] <- list()
    }
    if (is.null(found_lons)) {
      found_lons <- args[[obs]]$xyCoords$x
    } else if (!identical(args[[obs]]$xyCoords$x, found_lons)) {
      stop("All provided downscaleR objects must be on the same grid.")
    }
    if (is.null(found_lats)) {
      found_lats <- args[[obs]]$xyCoords$y
    } else if (!identical(args[[obs]]$xyCoords$y, found_lats)) {
      stop("All provided downscaleR objects must be on the same grid.")
    }
    obs_members <- 'Member_1'
    found_members <- lapply(provided_data[['obs']][[dataset]], '[[', 'members')
    matches <- sapply(found_members, identical, obs_members)
    obs_ftimes <- args[[obs]]$Dates$start
    if (is.null(found_sdates)) {
      found_sdates <- obs_ftimes[1]
    }
    if (any(matches)) {
      found_ftimes <- lapply(provided_data[['obs']][[dataset]][[which(matches)[1]]][['chunks']], '[[', 'ftimes')
      ftime_matches <- sapply(found_ftimes, identical, obs_ftimes)
      if (any(ftime_matches)) {
        stop("Provided twice the data for the same observation and range of forecast times.")
      } else {
        provided_data[['obs']][[dataset]][[which(matches)[1]]][['chunks']] <- c(provided_data[['obs']][[dataset]][[which(matches)[1]]][['chunks']], 
                                                                                list(
                                                                                  list(ftimes = args[[obs]]$Dates$start,
                                                                                       obs_index = obs
                                                                                      )
                                                                                    )
                                                                               )
      }
    } else {
      provided_data[['obs']][[dataset]] <- c(provided_data[['obs']][[dataset]], 
                                             list(
                                               list(members = obs_members, 
                                                    chunks = list(
                                                               list(ftimes = args[[obs]]$Dates$start,
                                                                    obs_index = obs
                                                                   )
                                                                 )
                                                   )
                                                 )
                                            )
    }
  }

  # THIS CODE IS ASSUMING NO OVERLAPPING SDATES COME FROM DOWSNCALER.
  # Casting a joint downscaleR object with all experimental data for each exp.
  s2dv_object <- NULL

  downr_exps <- list()
  for (exp_name in names(provided_data$exp)) {
    for (member in 1:length(provided_data$exp[[exp_name]])) {
      new_member <- NULL
      for (chunk in 1:length(provided_data$exp[[exp_name]][[member]][['chunks']])) {
        if (!is.null(new_member)) {
          new_member <- bindGrid.time(new_member,
                                      args[[provided_data$exp[[exp_name]][[member]][['chunks']][[chunk]][['exp_index']]]])
        } else {
          new_member <- args[[provided_data$exp[[exp_name]][[member]][['chunks']][[chunk]][['exp_index']]]]
        }
      }
      ## Temporary solution for the TODO
      if (is.null(found_dates)) {
        found_dates <- new_member$Dates$start
      } else if (!identical(found_dates, new_member$Dates$start)) {
        stop("Same dates must be provided for all observations.")
      }
      ##
      if (exp_name %in% names(downr_exps)) {
        ## TODO: If dates do not match with existing members or with other exps, extend downscaleR objects with NAs.
        downr_exps[[exp_name]] <- bindGrid.member(downr_exps[[exp_name]], new_member)
      } else {
        downr_exps[[exp_name]] <- new_member
      }
    }
    if (is.null(s2dv_object)) {
      s2dv_object <- DownRToS2dv1Dat(downr_exps[[exp_name]], found_sdates)
    } else {
      s2dv_object <- MergeS2dvEqualDats(s2dv_object, 
                                        DownRToS2dv1Dat(downr_exps[[exp_name]], 
                                                        found_sdates))
    }
    downr_exps[[exp_name]] <- NULL
  }
  # Casting a joint downscaleR object with all observational data for each obs.
  downr_obss <- list()
  for (obs_name in names(provided_data$obs)) {
    for (member in 1:length(provided_data$obs[[obs_name]])) {
      new_member <- NULL
      for (chunk in 1:length(provided_data$obs[[obs_name]][[member]][['chunks']])) {
        if (!is.null(new_member)) {
          new_member <- bindGrid.time(new_member,
                                      args[[provided_data$obs[[obs_name]][[member]][['chunks']][[chunk]][['obs_index']]]])
        } else {
          new_member <- args[[provided_data$obs[[obs_name]][[member]][['chunks']][[chunk]][['obs_index']]]]
        }
      }
      ## Temporary solution for the TODO
      if (is.null(found_dates)) {
        found_dates <- new_member$Dates$start
      } else if (!identical(found_dates, new_member$Dates$start)) {
        stop("Same dates must be provided for all observations.")
      }
      ##
      if (obs_name %in% names(downr_obss)) {
        stop("Not expected.")
        ## TODO: If dates do not match with existing members or with other exps, extend downscaleR objects with NAs.
        downr_obss[[obs_name]] <- bindGrid.member(downr_obss[[obs_name]], new_member)
      } else {
        downr_obss[[obs_name]] <- new_member
      }
    }
    if (is.null(s2dv_object)) {
      s2dv_object <- DownRToS2dv1Dat(downr_obss[[obs_name]], found_sdates)
    } else {
      s2dv_object <- MergeS2dvEqualDats(s2dv_object, 
                                        DownRToS2dv1Dat(downr_obss[[obs_name]],
                                                        found_sdates))
    }
    downr_obss[[obs_name]] <- NULL
  }

  s2dv_object
}
