#'Random Walk test for skill differences
#'
#'Forecast comparison of the skill obtained with 2 forecasts (with respect to a 
#'common observational reference) based on Random Walks (DelSole and Tippett, 2016).
#'The function is implemented for negatively oriented scores (i.e., the lower the
#'better). Examples of negatively oriented scores are the RPS, RMSE and the Error.
#'In case of positively oriented scores (i.e., the higher the better), please
#'multiply skill_A and skill_B by -1.
#'
#'@param skill_A A numerical array of the time series of the scores obtained
#'  with the forecaster A.
#'@param skill_B A numerical array of the time series of the scores obtained
#'  with the forecaster B. The dimensions should be identical as parameter 'skill_A'.
#'@param time_dim A character string indicating the name of the dimension along
#'  which the tests are computed. The default value is 'sdate'.
#'@param N.eff Effective sample size to be used in the statistical significance 
#'  test. It can be NA (to use the length of the "time_dim" dimension) or an
#'  array with the same dimensions as "skill_A" except "time_dim" (for a 
#'  particular N.eff to be used for each case). The default value is NA.
#'@param test.type A character string indicating the type of significance test. 
#'  It can be "two-sided" (to assess whether "skill_A" and "skill_B" are 
#'  significantly different) or "one-sided" (to assess whether "skill_A" is 
#'  significantly better than "skill_B"). In case of "one-sided" test, it matters
#'  whether the scores are positively or negatively oriented. The one-sided test 
#'  will be performed assuming negatively oriented scores. The default value is 
#'  "two-sided".
#'@param alpha A numeric of the significance level to be used in the statistical
#'  significance test. If it is a numeric, the significance will be returned. 
#'  If NULL, the p-value will be returned instead. The default value is 0.05.
#'@param ncores An integer indicating the number of cores to use for parallel 
#'  computation. The default value is NULL.
#'
#'@return A list with:
#'\item{$score}{
#'  A numerical array with the same dimensions as the input arrays except 
#'  'time_dim'. The number of times that forecaster A has been better than 
#'  forecaster B minus the number of times that forecaster B has been better 
#'  than forecaster A (for skill negatively oriented, i.e., the lower the 
#'  better). If $score is positive, forecaster A has been better more times
#'  than forecaster B. If $score is negative, forecaster B has been better more
#'  times than forecaster A.
#'}
#'\item{$signif}{
#'  A logical array of the statistical significance with the same dimensions
#'  as the input arrays except "time_dim". Returned only if "alpha" is a numeric.
#'}
#'\item{$p.val}{
#'  A numeric array of the p-values with the same dimensions as the input arrays
#'  except "time_dim". Returned only if "alpha" is NULL.
#'}
#'
#'@references 
#'DelSole and Tippett (2016): https://doi.org/10.1175/MWR-D-15-0218.1
#'
#'@examples
#' fcst_A <- array(data = 11:50, dim = c(sdate = 10, lat = 2, lon = 2))
#' fcst_B <- array(data = 21:60, dim = c(sdate = 10, lat = 2, lon = 2))
#' reference <- array(data = 1:40, dim = c(sdate = 10, lat = 2, lon = 2))
#' skill_A <- abs(fcst_A - reference)
#' skill_B <- abs(fcst_B - reference)
#' RandomWalkTest(skill_A = skill_A, skill_B = skill_B, time_dim = 'sdate', N.eff = NA, ncores = 1)
#'
#'@import multiApply
#'@export
RandomWalkTest <- function(skill_A, skill_B, time_dim = 'sdate', N.eff = NA, 
                           test.type = 'two-sided', alpha = 0.05, ncores = NULL){
  
  ## Check inputs
  ## skill_A and skill_B
  if (is.null(skill_A) | is.null(skill_B)){
    stop("Parameters 'skill_A' and 'skill_B' cannot be NULL.")
  }
  if(!is.numeric(skill_A) | !is.numeric(skill_B)){
    stop("Parameters 'skill_A' and 'skill_B' must be a numerical array.")
  }
  if (!identical(dim(skill_A),dim(skill_B))){
    stop("Parameters 'skill_A' and 'skill_B' must have the same dimensions.")
  }
  ## time_dim
  if(!is.character(time_dim) | length(time_dim) != 1){
    stop("Parameter 'time_dim' must be a character string.")
  }
  if(!time_dim %in% names(dim(skill_A)) | !time_dim %in% names(dim(skill_B))){
    stop("Parameter 'time_dim' is not found in 'skill_A' or 'skill_B' dimensions.")
  }
  ## N.eff
  if (is.array(N.eff)) {
    if (!is.numeric(N.eff)) stop("Parameter 'N.eff' must be numeric.")
    if (!all(names(dim(N.eff)) %in% names(dim(skill_A))) |
        any(dim(skill_A)[match(names(dim(N.eff)), names(dim(skill_A)))] != dim(N.eff))) {
      stop(paste0('If parameter "N.eff" is provided with an array, it must ',
                  'have the same dimensions as "skill_A" except "time_dim".'))
    }
  } else if (!is.na(N.eff)) {
    stop(paste0('Parameter "N.eff" must be NA or an array with ',
                'the same dimensions as "skill_A" except "time_dim".'))
  }
  ## test.type
  if (!test.type %in% c('two-sided', 'one-sided')) {
    stop("Parameter 'test.type' must be 'two-sided' or 'one-sided'.")
  }
  ## alpha
  if (!is.null(alpha)) {
    if (any(!is.numeric(alpha) | alpha <= 0 | alpha >= 1 |  
            length(alpha) > 1)) {
      stop('Parameter "alpha" must be NULL or a number between 0 and 1.')
    }
  }
  ## ncores
  if (!is.null(ncores)){
    if (!is.numeric(ncores) | ncores %% 1 != 0 | ncores <= 0 | length(ncores) > 1){
      stop("Parameter 'ncores' must be a positive integer.")
    }
  }
  
  ## Compute the Random Walk Test
  res <- multiApply::Apply(data = list(skill_A = skill_A,
                                       skill_B = skill_B,
                                       N.eff = N.eff), 
                           target_dims = list(skill_A = time_dim,
                                              skill_B = time_dim, 
                                              N.eff = NULL),
                           fun = .RandomWalkTest, 
                           test.type = test.type,
                           alpha = alpha,
                           ncores = ncores)
  return(res)
}

.RandomWalkTest <- function(skill_A, skill_B, N.eff = NA, test.type = 'two-sided', alpha = 0.05){
  
  ## For one-sided test, this function assumes that the scores are negatively oriented, i.e., the lower the better
  ## Thus, significance may only be reached if score<0 (i.e., forecaster A is better than forecaster B)
  ## For two-sided test, it does not matter the orientation of the scores 
  
  ## DelSole and Tippett (2016) approximation for two-sided test at 95% confidence level: significant if score is above or below 2*sqrt(N)
  # signif<- ifelse(test = (score <  (-2)*sqrt(N.eff)) | (score >  2*sqrt(N.eff)), yes = TRUE, no = FALSE)
  
  if (is.na(N.eff)){
    N.eff <- length(skill_A)
  }
  
  output <- NULL
  output$score <- cumsum(skill_A > skill_B) - cumsum(skill_A < skill_B)
  output$score <- -output$score[length(skill_A)] ## negative sign for the output to be positive in case A has been better more times
  
  if (test.type == 'one-sided'){
    
    ## H0: forecaster A is not better than forecaster B
    ## H1: forecaster A is better than forecaster B
    
    p.val <- pbinom(q = output$score, size = N.eff, prob = 0.5, lower.tail = FALSE, log.p = FALSE)
    
    if (is.null(alpha)){
      output$p.val <- p.val
    } else {
      output$signif <- ifelse(!is.na(p.val) & p.val <= alpha, TRUE, FALSE)
    }
    
  } else if (test.type == 'two-sided'){
    
    ## H0: forecaster A and forecaster B are not different in terms of skill
    ## H1: forecaster A and forecaster B are different in terms of skill
    
    p.val <- pbinom(q = abs(output$score), size = N.eff, prob = 0.5, lower.tail = FALSE, log.p = FALSE)
    
    if (is.null(alpha)){
      output$p.val <- p.val
    } else {
      output$signif <- ifelse(!is.na(p.val) & p.val <= alpha / 2, TRUE, FALSE)
    }
    
  } else {stop("Parameter 'test.type' is not supported.")}
  
  return(output)
}
