#'Signal-to-noise ratio
#'
#'This function computes the signal-to-noise ratio, where the signal is the ensemble 
#'mean variance and the noise is the variance of the ensemble members about the ensemble
#'mean (Eade et al., 2014; Scaife and Smith, 2018).
#'
#'@param exp A numerical array with, at least, 'time_dim' and 'member_dim' dimensions.
#'@param time_dim A character string indicating the name of the time dimension. The default value is 'year'.
#'@param member_dim A character string indicating the name of the member dimension. The default value is 'member'.
#'@param na.rm A logical value indicating whether to remove NA values during the computation. The default value is FALSE.
#'@param ncores An integer indicating the number of cores to use for parallel computation. The default value is NULL.
#'
#'@return An array with the signal-to-noise ratio with the same dimensions than 'exp' except 'time_dim' and 'member_dim' dimensions.
#'
#'@example
#' exp <- array(data = runif(600), dim = c(year = 15, member = 10, lat = 2, lon = 2))
#' SignalNoiseRatio(exp)
#'
#'@import multiApply
#'@export
SignalNoiseRatio <- function(exp, time_dim = 'year', member_dim = 'member', na.rm = FALSE, ncores = NULL) {
  
  ## Checkings
  if (is.null(exp)) {
    stop("Parameter 'exp' cannot be NULL.")
  }
  if (!is.numeric(exp)) {
    stop("Parameter 'exp' must be a numeric array.")
  }
  if (!(is.character(time_dim) & length(time_dim) == 1)) {
    stop("Parameter 'time_dim' must be a character string.")
  }
  if (!(is.character(member_dim) & length(member_dim) == 1)) {
    stop("Parameter 'member_dim' must be a character string.")
  }
  if (!time_dim %in% names(dim(exp))){
    stop("'exp' must have 'time_dim' dimension.")
  }
  if (!member_dim %in% names(dim(exp))){
    stop("'exp' must have 'member_dim' dimension.")
  }
  if (!na.rm %in% c(TRUE,FALSE)){
    stop("Parameter 'na.rm' must be TRUE or FALSE.")
  }
  if (!is.null(ncores)){
    if (!is.numeric(ncores) | ncores %% 1 != 0 | length(ncores) > 1){
      stop("Parameter 'ncores' must be a positive integer.")
    }
  }
  
  SNR <- multiApply::Apply(data = exp, 
                           target_dims = c(time_dim,member_dim), 
                           fun = .SignalNoiseRatio,
                           time_dim = time_dim,
                           member_dim = member_dim,
                           na.rm = na.rm,
                           ncores = ncores)$output1
  return(SNR)
}

.SignalNoiseRatio <- function(exp = exp, time_dim = time_dim, member_dim = member_dim, na.rm = na.rm) {
  
  n_times <- as.numeric(dim(exp)[time_dim])
  n_members <- as.numeric(dim(exp)[member_dim])
  
  ## Mean (climatology)
  exp_mean <- multiApply::Apply(data = exp, target_dims = c(time_dim,member_dim), fun = mean, na.rm = na.rm)$output1
  ## Ensemble mean
  ens_mean <- multiApply::Apply(data = exp, target_dims = member_dim, fun = mean, na.rm = na.rm)$output1
  ## Ensemble mean variance -> signal
  var_signal <- (1 / n_times) * multiApply::Apply(data = (ens_mean - exp_mean)^2, target_dims = time_dim, fun = sum, na.rm = na.rm)$output1
  ## Variance of ensemble members about ensemble mean (= spread) -> noise
  var_noise <- (1 / n_times) * (1 / n_members) * multiApply::Apply(data = (multiApply::Apply(data = list(exp,ens_mean), target_dims = time_dim, fun = function(exp,ens_mean){exp-ens_mean}, ncores = ncores)$output1)^2, 
                                                                   target_dims = c(time_dim, member_dim), fun = sum, na.rm = na.rm)$output1
  ## Signal to noise ratio
  SNR <- var_signal / var_noise
  
  return(SNR)
}