##############################################

# dat
data_exp = array(1:20, dim = c(dataset = 1, member = 15, sdate = 6, ftime = 3, lat = 5, lon = 5))
lon <- seq(0, 4)
lat <- seq(1, 5)
coords <- list(lon = lon, lat = lat)
attrs_exp = list(Datasets = 'exp_sample_data')
exp <- list(data = data_exp, coords = coords, attrs = attrs_exp)
class(exp) <- 's2dv_cube'
data_obs <- array(1:20, dim = c(dataset = 1, member = 1, sdate = 6, ftime = 3, lat = 5, lon = 5))
attrs_obs = list(Datasets = 'obs_sample_data')
obs <- list(data = data_obs, coords = coords, attrs = attrs_obs)
class(obs) <- 's2dv_cube'

# dat1
exp1 <- list(data = array(1:20, dim = c(time = 20)))
class(exp1) <- 's2dv_cube'
obs1 <- list(data = array(1:20, dim = c(time = 20)))
class(obs1) <- 's2dv_cube'
exp1_2 <- list(data = array(1:20, dim = c(20)))
class(exp1_2) <- 's2dv_cube'
obs1_2 <- list(data = array(1:20, dim = c(20)))
class(obs1_2) <- 's2dv_cube'
exp_cor1 <- list(data = array(1:20, dim = c(20)))
class(exp_cor1) <- 's2dv_cube'

# dat2
exp2 <- exp
exp2$data[1, 2, 1, 1, 1, 1] <- NA
obs2 <- obs
obs2$data[1, 1, 2, 1, 1, 1] <- NA

# dat3
set.seed(1)
exp3 <- array(rnorm(400), dim = c(member = 10, sdate = 10, lat = 2, dataset = 2))
set.seed(2)
obs3 <- array(rnorm(60), dim = c(sdate = 10, lat = 2, dataset = 3))
set.seed(3)
exp_cor3 <- array(rnorm(200), dim = c(member = 10, sdate = 10, lat = 2))

# dat4
set.seed(1)
exp4 <- array(rnorm(200), dim = c(member = 10, sdate = 20))
exp4_1 <- array(exp4, dim = c(member = 10, sdate = 20, dataset = 1))
exp4_2 <- exp4_1
exp4_2[1] <- NA
set.seed(2)
obs4 <- array(rnorm(20), dim = c(sdate = 20))
obs4_1 <- array(obs4, dim = c(sdate = 20, dataset = 1))
obs4_2 <- obs4_1
obs4_2[c(1, 3)] <- NA
set.seed(3)
exp_cor4 <- array(rnorm(200), dim = c(member = 10, sdate = 20))

# dat5
exp_cor5 <- array(rnorm(400), dim = c(member = 10, sdate = 10, lat = 2, dataset = 2))

##############################################

test_that("1. Input checks", {
  # s2dv_cube
  expect_error(
    CST_Calibration(exp = 1),
    paste0("Parameter 'exp' and 'obs' must be of the class 's2dv_cube'.")
  )
  expect_error(
    CST_Calibration(exp = exp, obs = 1),
    paste0("Parameter 'exp' and 'obs' must be of the class 's2dv_cube'.")
  )
  expect_error(
    CST_Calibration(exp = exp1),
    'argument "obs" is missing, with no default'
  )
  expect_error(
    CST_Calibration(exp = exp1, obs = obs1, exp_cor = 1),
    paste0("Parameter 'exp_cor' must be of the class 's2dv_cube'.")
  )
  # exp and obs
  expect_error(
    Calibration(exp = 1, obs = obs1),
    "Parameter 'exp' must be a numeric array."
  )
  expect_error(
    CST_Calibration(exp = exp1_2, obs = obs1),
    "Parameter 'exp' must have dimension names."
  )
  expect_error(
    CST_Calibration(exp = exp1, obs = obs1_2),
    "Parameter 'obs' must have dimension names."
  )
  expect_warning(
    CST_Calibration(exp = exp2, obs = obs2, exp_cor = exp2),
    "Parameter 'obs' contains NA values",
    "Parameter 'exp' contains NA values.",
    "Parameter 'exp_cor' contains NA values."
  )
  # exp_cor
  expect_error(
    CST_Calibration(exp = exp1, obs = obs1, exp_cor = exp_cor1, sdate_dim = 'time'),
    "Parameter 'exp_cor' must have dimension names."
  )
  # dat_dim
  expect_error(
    Calibration(exp = exp3, obs = obs3, dat_dim = 1),
    paste0("Parameter 'dat_dim' must be a character string.")
  )
  expect_error(
    CST_Calibration(exp = exp, obs = obs, dat_dim = 'dat'),
    paste0("Parameter 'dat_dim' is not found in 'exp' or 'obs' dimension.",
           " Set it as NULL if there is no dataset dimension.")
  )
  # sdate_dim, memb_dim
  expect_error(
    CST_Calibration(exp = exp1, obs = obs1, sdate_dim = 1),
    paste0("Parameter 'sdate_dim' should be a character string indicating the",
           "name of the dimension where start dates are stored in 'exp'.")
  )
  expect_warning(
    CST_Calibration(exp = exp, obs = obs, sdate_dim = c('sdate', 'time')),
    paste0("Parameter 'sdate_dim' has length greater than 1 and only",
           " the first element will be used.")
  )
  expect_error(
    CST_Calibration(exp = exp1, obs = obs1, memb_dim = 1),
    paste0("Parameter 'memb_dim' should be a character string indicating the",
           "name of the dimension where members are stored in 'exp'.")
  )
  expect_error(
    CST_Calibration(exp = exp, obs = obs, sdate_dim = 'time'),
    paste0("Parameter 'exp' requires 'sdate_dim' and 'memb_dim' dimensions.")
  )
  expect_warning(
    CST_Calibration(exp = exp, obs = obs, memb_dim = c('member', 'memb')),
    paste0("Parameter 'memb_dim' has length greater than 1 and only",
           " the first element will be used.")
  )
  expect_error(
    CST_Calibration(exp = exp1, obs = obs1, sdate_dim = 'time'),
    paste0("Parameter 'exp' requires 'sdate_dim' and 'memb_dim' dimensions.")
  )
  expect_error(
    Calibration(exp = array(1:20, dim = c(time = 1, member = 1)), 
                obs = array(1:20, dim = c(member = 1)), sdate_dim = 'time'),
    paste0("Parameter 'obs' must have the dimension defined in sdate_dim ",
           "parameter.")
  )
  expect_warning(
    Calibration(exp = array(abs(rnorm(400)), dim = c(sdate = 4, member = 10)), 
                obs = array(abs(rnorm(8)), dim = c(sdate = 4, member = 2))),
    paste0("Parameter 'obs' has dimension 'memb_dim' with length larger",
           " than 1. Only the first member dimension will be used.")
  )
  # exp, obs, and exp_cor (2)
  expect_error(
    Calibration(exp = array(1:20, dim = c(time = 1, member = 1)), 
                obs = array(1:20, dim = c(time = 2, member = 1)), sdate_dim = 'time'),
    paste0("Parameter 'exp' and 'obs' must have same length of all dimensions", 
           " except 'memb_dim' and 'dat_dim'.")
  )
  expect_error(
    Calibration(exp = array(1:6, c(sdate = 3, member = 2, dataset = 2, lon = 1)), 
                   obs = array(1:6, c(sdate = 3, member = 1, dataset = 3, lon = 2)), 
                   dat_dim = 'dataset'),
    paste0("Parameter 'exp' and 'obs' must have same length of all dimensions", 
           " except 'memb_dim' and 'dat_dim'.")
  )
  expect_error(
    Calibration(exp = array(1:6, c(sdate = 3, member = 2, dataset = 2, lon = 1)), 
                obs = array(1:6, c(sdate = 3, member = 1, dataset = 3, lon = 1)),
                exp_cor = array(1:6, c(sdate = 3, member = 1, dataset = 3, lon = 1)), 
                dat_dim = 'dataset'),
    paste0("If parameter 'exp_cor' has dataset dimension, it must be", 
           " equal to dataset dimension of 'exp'.")
  )
  expect_error(
    Calibration(exp = array(1:6, c(sdate = 3, member = 2, dataset = 2, lon = 1)), 
                obs = array(1:6, c(sdate = 3, member = 1, dataset = 3, lon = 1)),
                exp_cor = array(1:6, c(sdate = 3, member = 2, lon = 2)), dat_dim = 'dataset'),
    paste0("Parameter 'exp' and 'exp_cor' must have the same length of ",
           "all common dimensions except 'dat_dim', 'sdate_dim' and 'memb_dim'.")
  )
  # ncores
  expect_error(
    CST_Calibration(exp = exp, obs = obs, ncores = TRUE),
    "Parameter 'ncores' must be either NULL or a positive integer."
  )
  # na.rm
  expect_error(
    CST_Calibration(exp = exp, obs = obs, na.rm = 1),
    "Parameter 'na.rm' must be a logical value."
  )
  expect_warning(
    CST_Calibration(exp = exp, obs = obs, na.rm = c(T,F)),
    "Paramter 'na.rm' has length greater than 1, and only the fist element is used."
  )
  # cal.method
  expect_error(
    Calibration(exp4, obs4, cal.method = 'biass'),
    "Parameter 'cal.method' must be a character string indicating the calibration method used."
  )
  expect_warning(
    Calibration(exp4, obs4, cal.method = 'rpc-based'),
    paste0("Parameter 'apply_to' cannot be NULL for 'rpc-based' method so it ", 
           "has been set to 'sign', as in Eade et al. (2014)."), 
    fixed = TRUE
  )
  expect_warning(
    Calibration(exp4, obs4, cal.method = 'rpc-based', apply_to = 'sign'),
    paste0("Parameter 'alpha' cannot be NULL for 'rpc-based' method so it ", 
           "has been set to 0.1, as in Eade et al. (2014)."), 
    fixed = TRUE
  )
  expect_error(
    Calibration(exp4, obs4, cal.method = 'rpc-based', apply_to = 'sign', alpha = 'a'),
    "Parameter 'alpha' must be a number between 0 and 1."
  )
  # eval.method
  expect_error(
    Calibration(exp4, obs4, eval.method = 'biass'),
    paste0("Parameter 'eval.method' must be a character string indicating ", 
           "the sampling method used ('in-sample', 'leave-one-out' or ", 
           "'hindcast-vs-forecast')."), 
    fixed = TRUE
  )
  # multi.model
  expect_error(
    Calibration(exp4, obs4, multi.model = 'biass'),
    "Parameter 'multi.model' must be a logical value."
  )
  expect_warning(
    Calibration(exp4, obs4, multi.model = TRUE, cal.method = 'bias'),
    paste0("The 'multi.model' parameter is ignored when using the ", 
           "calibration method 'bias'.")
  )
})

##############################################

test_that("2. Output checks: dat1", {
  # dat_dim = NULL
  cal <- CST_Calibration(exp = exp, obs = obs)
  expect_equal(
    length(cal), 
    3
  )
  expect_equal(
    as.numeric(dim(cal$data)),
    as.numeric(dim(exp$data))
  )
  expect_equal(
    cal$coords$lat, 
    exp$coords$lat
  )
  expect_equal(
    cal$coords$lat, 
    obs$coords$lat
  )
  expect_equal(
    cal$coords$lon, 
    exp$coords$lon
  )
  expect_equal(
    cal$coords$lon, 
    obs$coords$lon
  )
  expect_equal(
    dim(cal$data),
    c(dataset = 1, member =15, sdate = 6, ftime = 3, lat = 5, lon = 5)
  )
  expect_equal(
    as.vector(cal$data)[1:5],
     c(1.594311, 1.861058, 2.127805, 2.394551, 2.661298),
     tolerance = 0.0001
  )
})  

##############################################

test_that("3. Output checks: dat3", {
  # dat_dim = 'dataset'
  # exp_cor = NULL
  expect_equal(
    dim(Calibration(exp3, obs3, memb_dim = 'member', dat_dim = 'dataset')),
    c(member = 10, sdate = 10, lat = 2, nexp = 2, nobs = 3)
  )
  expect_equal(
    dim(Calibration(exp3, obs3, exp_cor3, memb_dim = 'member', dat_dim = 'dataset')),
    c(member = 10, sdate = 10, lat = 2, nexp = 2, nobs = 3)
  )
  expect_equal(
    as.vector(Calibration(exp3, obs3, memb_dim = 'member', dat_dim = 'dataset'))[5:10],
    c(0.5462052, -0.6882557, 0.7157284, 0.9850565, 0.8105716, -0.1353346),
    tolerance = 0.0001
  )
  expect_equal(
    as.vector(Calibration(exp3, obs3, exp_cor3, memb_dim = 'member', dat_dim = 'dataset'))[5:10],
    c(0.4908992, 0.3054365, 0.3673404, 1.5218074, -1.0928551, 1.6905885),
    tolerance = 0.0001
  )
  expect_equal(
    as.vector(Calibration(exp3[, , , 1], obs3[, , 1], memb_dim = 'member'))[1:5],
    c(-0.4799875, 0.3896246, -0.7045297, 1.9049701, 0.5462052), 
    tolerance = 0.0001
  )
  expect_equal(
    as.vector(Calibration(exp3[, , , 1], obs3[, , 1], memb_dim = 'member'))[1:5],
    as.vector(Calibration(exp3, obs3, memb_dim = 'member', dat_dim = 'dataset')[, , , 1, 1][1:5]),
    tolerance = 0.0001
  )
  expect_equal(
    as.vector(Calibration(exp3[, , , 1], obs3[, , 1], exp_cor3, eval.method = "hindcast-vs-forecast"))[1:5],
    c(-0.80521700, -0.05578454, 0.56143657, -1.01815286, 0.49089916), 
    tolerance = 0.0001
  )
  expect_equal(
    as.vector(Calibration(exp3[, , , 1], obs3[, , 1], exp_cor3, memb_dim = 'member'))[1:5],
    as.vector(Calibration(exp3, obs3, exp_cor3, memb_dim = 'member', dat_dim = 'dataset')[, , , 1, 1][1:5])
  )
  expect_equal(
    as.vector(Calibration(exp3, obs3, exp_cor3, memb_dim = 'member', dat_dim = 'dataset', na.rm = TRUE))[1:5],
    c(-0.80521700, -0.05578454, 0.56143657, -1.01815286, 0.49089916),
    tolerance = 0.0001
  )
})

##############################################

test_that("4. Output checks: dat3", {
  # Check ca.method
  expect_equal(
    as.vector(Calibration(exp3, obs3, memb_dim = 'member', dat_dim = 'dataset', cal.method = 'bias'))[1:7],
    c(-0.3984805, 0.4116167, -0.6076553, 1.8232541, 0.5574811, -0.5924950, 0.7154024),
    tolerance = 0.0001
  )
  expect_equal(
    as.vector(Calibration(exp3, obs3, exp_cor3, memb_dim = 'member', dat_dim = 'dataset', cal.method = 'evmos'))[1:7],
    c(-0.9631369, -0.2290479, 0.3755366, -1.1717133, 0.3064434, 0.1247777, 0.1854143),
    tolerance = 0.0001
  )
  expect_equal(
    as.vector(Calibration(exp3, obs3, memb_dim = 'member', dat_dim = 'dataset', cal.method = 'mse_min', multi.model = TRUE))[5:10],
    c(0.5364620, -0.6412113, 0.6981868, 0.9551252, 0.7886670, -0.1137257),
    tolerance = 0.0001
  )
  expect_equal(
    as.vector(Calibration(exp3, obs3, exp_cor3, memb_dim = 'member', dat_dim = 'dataset', cal.method = 'crps_min'))[5:10],
    c(0.4975732, 0.3078346, 0.3717602, 1.5607014, -1.1259947, 1.7316233),
    tolerance = 0.0001
  )
  expect_equal(
    as.vector(Calibration(exp3, obs3, exp_cor3, memb_dim = 'member', dat_dim = 'dataset', cal.method = 'rpc-based', alpha = 0.05, apply_to = 'all'))[5:10],
    c(0.4178384, 0.2323757, 0.2942796, 1.4487467, -1.1659159, 1.6175277),
    tolerance = 0.0001
  )
})

##############################################

test_that("5. Output checks: dat4", {
  expect_equal(
    dim(Calibration(exp4, obs4)),
    c(member = 10, sdate = 20)
  )
  expect_equal(
    as.vector(Calibration(exp4, obs4))[1:5],
    c(-0.6372505, 0.3261267, -0.8860036, 2.0048627, 0.4995904),
    tolerance = 0.0001
  )
  expect_equal(
    as.vector(Calibration(exp4, obs4, exp_cor4, eval.method = "hindcast-vs-forecast"))[1:5],
    c(-0.88657225, -0.08128503, 0.58193721, -1.11537810, 0.50614269),
    tolerance = 0.0001
  )

  expect_equal(
    as.vector(Calibration(exp4_1, obs4_1))[1:6],
    c(-0.6372505, 0.3261267, -0.8860036, 2.0048627, 0.4995904, -0.8679749),
    tolerance = 0.0001
  )
  # exp_cor4 doesn't have dat_dim
  expect_equal(
    as.vector(Calibration(exp4_1, obs4_1, exp_cor4, dat_dim = 'dataset')),
    as.vector(Calibration(exp4, obs4, exp_cor4)),
    tolerance = 0.0001
  )
  expect_equal(
    suppressWarnings(
      as.vector(Calibration(exp4_1, obs4_2, dat_dim = 'dataset', na.rm = FALSE))
    ),
    rep(as.numeric(NA), 200)
  )
  expect_equal(
    suppressWarnings(
      as.vector(Calibration(exp4_1, obs4_2, dat_dim = 'dataset', na.rm = T))[5:10]
    ),
    c(0.4343443, -0.9018916, 0.6178439, 0.9093767, 0.7205064, -0.3033850),
    tolerance = 0.0001
  )
  expect_equal(
    suppressWarnings(
      as.vector(Calibration(exp4_2, obs4_2, exp_cor4, dat_dim = 'dataset', na.rm = T))[5:10]
    ),
    c(0.4583975, 0.2645130, 0.3292279, 1.5361189, -1.1972745, 1.7125642),
    tolerance = 0.0001
  )
  expect_equal(
    dim(Calibration(exp4_1, obs4_1, dat_dim = 'dataset')),
    c(member = 10, sdate = 20, nexp = 1, nobs = 1)
  )
  # exp_cor5 with dat_dim
  expect_equal(
    dim(Calibration(exp3, obs3, exp_cor5, dat_dim = 'dataset')),
    c(member = 10, sdate = 10, lat = 2, nexp = 2, nobs = 3)
  ) 
})

##############################################

test_that("6. Output checks: dat4", {
  # dat_dim = NULL
  expect_equal(
    as.vector(Calibration(exp4, obs4, cal.method = 'bias'))[1:7],
    c(-0.4039514, 0.4061457, -0.6131262, 1.8177832, 0.5520101, -0.5979660, 0.7099314),
    tolerance = 0.0001
  )
  expect_equal(
    as.vector(Calibration(exp4, obs4, cal.method = 'evmos'))[1:7],
    c(-0.4860875, 0.4252999, -0.7214164, 2.0134410, 0.5894025, -0.7043606, 0.7670694),
    tolerance = 0.0001
  )
  expect_equal(
    as.vector(Calibration(exp4, obs4, cal.method = 'mse_min', multi.model = TRUE))[1:7],
    c(-0.5932137, 0.3231407, -0.8298251, 1.9199372, 0.4881377, -0.8126764, 0.6667729),
    tolerance = 0.0001
  )
  expect_equal(
    as.vector(Calibration(exp4, obs4, cal.method = 'crps_min'))[1:7],
    c(-0.6381684, 0.3261889, -0.8871746, 2.0066329, 0.4998291, -0.8691275, 0.6878220),
    tolerance = 0.0001
  )
  expect_equal(
    as.vector(Calibration(exp4, obs4, cal.method = 'rpc-based', alpha = 0.5, apply_to = 'sign'))[1:7],
    c(-0.8597528, 0.1036243, -1.1085060, 1.7823603, 0.2770880, -1.0904773, 0.4648899),
    tolerance = 0.0001
  )
  expect_equal(
    as.vector(Calibration(exp4, obs4, exp_cor4, cal.method = 'rpc-based', alpha = 0.5, apply_to = 'sign', eval.method = "hindcast-vs-forecast"))[1:7],
    c(-1.0464936, -0.2412064, 0.4220158, -1.2752995, 0.3462213, 0.1469362, 0.2134538),
    tolerance = 0.0001
  )
  expect_equal(
    as.vector(Calibration(exp4, obs4, eval.method = "in-sample"))[1:5],
    c(-0.7119142, 0.2626203, -0.9635483, 1.9607986, 0.4380930),
    tolerance = 0.0001
  )
})

##############################################

test_that("7. Output checks: dat4", {
  expect_warning(
    Calibration(exp = array(1, dim = c(member = 1, sdate = 1)), 
                obs = array(1, dim = c(sdate = 1))), 
    "Some forecast data could not be corrected due to data lack and is replaced with NA values."
  )
  expect_warning(
    Calibration(exp = array(1, dim = c(member = 1, sdate = 1)), 
                obs = array(1, dim = c(sdate = 1)), na.fill = FALSE), 
    "Some forecast data could not be corrected due to data lack and is replaced with uncorrected values."
  )
  suppressWarnings(
    expect_equal(
      dim(Calibration(exp = array(1, dim = c(dataset = 1, member = 1, sdate = 1)), 
                      obs = array(1, dim = c(dataset = 1, sdate = 1)), 
                      dat_dim = 'dataset', na.fill = FALSE)), 
      c(member = 1, sdate = 1, nexp = 1, nobs = 1)
    )
  )
  suppressWarnings(
    expect_equal(
      dim(Calibration(exp = array(1, dim = c(dataset = 3, member = 1, sdate = 1)), 
                      obs = array(1, dim = c(dataset = 2, sdate = 1)), 
                      exp_cor = array(1, dim = c(dataset = 3, member = 1, sdate = 1)),
                      dat_dim = 'dataset', na.fill = FALSE)), 
      c(member = 1, sdate = 1, nexp = 3, nobs = 2)
    )
  )
  suppressWarnings(
    expect_equal(
      dim(Calibration(exp = array(1, dim = c(dataset = 1, member = 1, sdate = 1)), 
                      obs = array(1, dim = c(dataset = 2, sdate = 1)), 
                      exp_cor = array(1, dim = c(member = 1, sdate = 1)),
                      dat_dim = 'dataset', na.fill = FALSE)), 
      c(member = 1, sdate = 1, nexp = 1, nobs = 2)
    )
  )
  # Check values
  suppressWarnings(
    expect_equal(
      Calibration(exp = array(1:6, dim = c(member = 2, sdate = 3)), 
                  obs = array(1:3, dim = c(sdate = 3)), 
                  na.fill = FALSE), 
      array(1:6, dim = c(member = 2, sdate = 3))
    )
  )
  suppressWarnings(
    expect_equal(
      Calibration(exp = array(1:6, dim = c(member = 2, sdate = 3)), 
                  obs = array(1:3, dim = c(sdate = 3))), 
      array(dim = c(member = 2, sdate = 3))
    )
  )
  suppressWarnings(
    expect_equal(
      Calibration(exp = array(1:6, dim = c(member = 2, sdate = 3)), 
                  obs = array(1:3, dim = c(sdate = 3)), 
                  exp_cor = array(6:12, dim = c(member = 2, sdate = 3)),
                  na.fill = FALSE), 
      array(1:6, dim = c(member = 2, sdate = 3))
    )
  )
})
