#' @rdname CST_EMBestIndexWeighting
#' @title Computing the weighted ensemble means for SFSs of a CSTools object.
#' @author Eroteida Sanchez-Garcia - AEMET, \email{esanchezg@aemet.es}
#' @description This function implements the computation to obtain the weighted
#' ensemble means for SFSs through Best estimation of one index or driver of
#' climate variability (as e.g., the North Atlantic Oscillation (NAO)) and
#' accepts a CSTools object (an object of the class 's2dv_cube') as input.
#' @references Regionally improved seasonal forecast of precipitation through
#' Best estimation of winter NAO, Sanchez-Garcia, E. et al.,
#' Adv. Sci. Res., 16, 165174, 2019, https://doi.org/10.5194/asr-16-165-2019
#' @param var_exp An object of the class 's2dv_cube' containing the variable
#' (e.g. precipitation, temperature, NAO index) array.
#' The var_exp object is expected to have an element named \code{$data} with
#' at least two dimensions (time, member).
#' If the variable is spatial the dimensions would be (time, member, lat, lon).
#' If we want to compute for several SFSs, the array could have another
#' dimension (time, dataset, member, lat, lon).
#' @param weights_index_exp An object of the class 's2dv_cube' containing
#' the normalized weights array.
#' The weights_index_exp object is expected to have an element named
#' \code{$data} with at least two dimensions (time, member).
#' If we want to compute for several SFSs, the array could have another
#' dimension (time, dataset, member).
#' @return CST_EMBestIndexWeighting() returns a CSTools object (i.e., of the
#' class 's2dv_cube').
#' This object has at least an element named \code{$data}
#' with three or five dimensions depending if the variable is zonal
#' (as e.g. NAO index)(time, dataset, season) or it is spatial variable
#' (as e.g. precipitation or temperature) (time, dataset, season, lat, lon),
#' containing the ensemble means computing with weighted members.
#' @export
#' @examples
#' var_exp <- 1 : (4 * 2 * 3 * 2 * 3 * 2)
#' dim(var_exp) <- c(time = 4, dataset = 2, member = 3, season = 2, lat = 3, lon = 2)
#' weights_index_exp <- runif(4 * 2 * 3 * 2, 0.0001, 0.9999)
#' dim(weights_index_exp) <- c(time = 4, dataset = 2, member = 3, season = 2)
#' var_exp <- list(data = var_exp)
#' weights_index_exp <- list(data = weights_index_exp)
#' class(var_exp) <- 's2dv_cube'
#' class(weights_index_exp) <- 's2dv_cube'
#' res_CST <- CST_EMBestIndexWeighting(var_exp, weights_index_exp)
#' dim(res_CST$data)
#' # time dataset  season     lat     lon
#' #    4       2       2       3       2
CST_EMBestIndexWeighting <- function(var_exp, weights_index_exp) {
  if (!inherits(var_exp, 's2dv_cube') || !inherits(weights_index_exp, 's2dv_cube')) {
    stop("Parameter 'var_exp' and 'weights_index_exp' must be of the class 's2dv_cube', ",
         "as output by CSTools::CST_Load.")
  }
  if (!any(names(dim(var_exp$data)) == 'time')){
    stop("The component 'data' of the parameter 'var_exp' must have the ",
         "dimension 'time'")
  }
  if (!any(names(dim(weights_index_exp$data)) == 'time')){
    stop("The component 'data' of the parameter 'weights_index_exp' must have ",
         "the dimension 'time'")
  }
  if (!any(names(dim(var_exp$data)) == 'member')){
    stop("The component 'data' of the parameter 'var_exp' must have the ",
         "dimension 'member'")
  }
  if (!any(names(dim(weights_index_exp$data)) == 'member')){
    stop("The component 'data' of the parameter 'weights_index_exp' must have ",
         "the dimension 'member'")
  }
  if (dim(var_exp$data)['time'] != dim(weights_index_exp$data)['time']){
    stop("The length of the dimension 'time' of the parameter 'var_exp' ",
         "in the component 'data' must be equal to the length of the dimension",
         " 'time' of the parameter 'weights_index_exp' in the component 'data'")
  }
  if (dim(var_exp$data)['member'] != dim(weights_index_exp$data)['member']){
    stop("The length of the dimension 'member' of the parameter 'var_exp' ",
         "in the component 'data' must be equal to the length of the dimension",
         " 'member' of the parameter 'weights_index_exp' in the component 'data'")
  }
  if (any(names(dim(weights_index_exp$data)) == 'lat') &
      dim(weights_index_exp$data)['lat'] != 1) {
    stop("The length of the dimension 'lat' of the parameter 'weights_index_exp'",
         " in the component 'data' must be equal to 1")
  }
  if (any(names(dim(weights_index_exp$data)) == 'lon') &
      dim(weights_index_exp$data)['lon'] != 1) {
    stop("The length of the dimension 'lon' of the parameter 'weights_index_exp'",
         " in the component 'data' must be equal to 1")
  }
  if (any(names(dim(weights_index_exp$data)) == 'lon')) {
    Dim <- dim(weights_index_exp$data)
    dim(weights_index_exp$data) <- Dim[-which(names(Dim) == 'lon')]
  }
  if (any(names(dim(weights_index_exp$data)) == 'lat')) {
    Dim <- dim(weights_index_exp$data)
    dim(weights_index_exp$data) <- Dim[-which(names(Dim) == 'lat')]
  }
  if (any(names(dim(weights_index_exp$data)) == 'lon')) {
    Dim <- dim(weights_index_exp$data)
    dim(weights_index_exp$data) <- Dim[-which(names(Dim) == 'lon')]
  }
  if (any(names(dim(var_exp$data)) == 'dataset') &
      !any(names(dim(weights_index_exp$data)) == 'dataset')) {
    stop("The component 'data' of the parameter 'weights_index_exp' must have'",
         " the dimension 'dataset'")
  }
  if (!any(names(dim(var_exp$data)) == 'dataset') &
      any(names(dim(weights_index_exp$data)) == 'dataset')) {
    stop("The component 'data' of the parameter 'var_exp' must have'",
         " the dimension 'dataset'")
  }
  if (any(names(dim(var_exp$data)) == 'dataset') &
      any(names(dim(weights_index_exp$data)) == 'dataset') &
      dim(var_exp$data)['dataset'] != dim(weights_index_exp$data)['dataset']) {
    stop("The length of the dimension 'dataset' of the parameter 'var_exp' ",
         "in the component 'data' must be equal to the length of the dimension",
         " 'dataset' of the parameter 'weights_index_exp' in the component 'data'")
  }

  em <- EMBestIndexWeighting(var_exp$data, weights_index_exp$data)
  em_var_exp <- list()
  em_var_exp$data <- em
  em_var_exp$Datasets <- var_exp$Datasets
  em_var_exp$source_files <- var_exp$source_files
  em_var_exp$Dates <- var_exp$Dates
  return(em_var_exp)
}

#' @rdname EMBestIndexWeighting
#' @title Computing the weighted ensemble means for SFSs.
#' @author Eroteida Sanchez-Garcia - AEMET, \email{esanchezg@aemet.es}
#' @description This function implements the computation to obtain the weighted
#' ensemble means for SFSs through Best estimation of one index or driver of
#' climate variability (as e.g., the North Atlantic Oscillation (NAO)).
#' @references Regionally improved seasonal forecast of precipitation through Best
#' estimation of winter NAO, Sanchez-Garcia, E. et al.,
#' Adv. Sci. Res., 16, 165174, 2019, https://doi.org/10.5194/asr-16-165-2019
#' @param var_exp Variable (e.g. precipitation, temperature, NAO index)
#' array from a SFS with at least two dimensions (time, member).
#' If the variable is spatial the dimensions would be (time, member, lat, lon).
#' If we want to compute for several SFSs, the array could have another
#' dimension (time, dataset, member, lat, lon).
#' @param weights_index_exp Normalized weights array with at least two
#' dimensions (time, member). If we want to compute for several SFSs,
#' the array could have another dimension (time, dataset, member).
#' @return EMBestIndexWeighting() returns an array with two or four dimensions
#' depending if the variable is zonal (as e.g. NAO index)(time, dataset)
#' or it is spatial variable (as e.g. precipitation or temperature)
#' (time, dataset, lat, lon), containing the ensemble means computing
#' with weighted members.
#' @import multiApply
#' @export
#' @examples
#' # Example 1 for the EMBestIndexWeighting function
#' var_exp <- 1 : (2 * 3 * 4)
#' dim(var_exp) <- c(time = 2, dataset = 3, member = 4)
#' weights_index_exp<- runif(24, min=0.001, max=0.999)
#' dim(weights_index_exp) <- c(time = 2, dataset = 3, member = 4)
#' res <- EMBestIndexWeighting(var_exp, weights_index_exp)
#' dim(res)
#' # time dataset
#' #    2       3
#' str(res)
#' # num [1:2, 1:3] 36.2 28.8 38.3 31.9 31 ...
#' # Example 2 for the EMBestIndexWeighting function
#' var_exp <- 1 : (2 * 3 * 4 * 2 * 3)
#' dim(var_exp) <- c(time = 2, dataset = 3, member = 4, lat = 2, lon = 3)
#' weights_index_exp<- runif(24, min=0.001, max=0.999)
#' dim(weights_index_exp) <- c(time = 2, dataset = 3, member = 4)
#' res <- EMBestIndexWeighting(var_exp, weights_index_exp)
#' dim(res)
#' # time dataset     lat     lon
#' #    2       3       2       3
#' str(res)
#' # num [1:2, 1:3, 1:2, 1:3] 30.7 10.6 30.8 19.7 35.1 ...
#'
EMBestIndexWeighting <- function(var_exp, weights_index_exp) {
  return(Apply(list(var_exp, weights_index_exp),
               target_dims = list(c('time','member'), c('time','member')),
               fun = .EMBestIndexWeighting)$output1)
}

#' Atomic EMBestIndexWeighting
#' @param var_exp Variable (e.g. precipitation, temperature, NAO index)
#' array from a SFS with dimensions (time, member).
#' @param weights_index_exp Normalized weights array with dimensions
#' (time, member).
#' @return .EMBestIndexWeighting returns an array of with dimensions (time),
#' the ensemble means computing with weighted members.
#' @examples
#' # Example for the Atomic EMBestIndexWeighting function
#' var_exp <- 1 : 6
#' dim(var_exp) <- c(time = 2, member = 3)
#' weights_index_exp <- c(0.28, 0.15, 0.69, 0.64, 0.42, 0.17)
#' dim(weights_index_exp) <- c(time = 2, member = 3)
#' res <- .EMBestIndexWeighting(var_exp, weights_index_exp)
#' str(res)
#' # num [1:2(1d)] 4.45 3.88
#' dim(res)
#' # time
#' #    2
#' @noRd
.EMBestIndexWeighting <- function(var_exp, weights_index_exp) {
  posTime <- match('time', names(dim(var_exp)))
  var_exp_em <- apply(var_exp * weights_index_exp, posTime, sum)
  dim(var_exp_em) <- c(time = length(var_exp_em))
  return(var_exp_em)
}
