# Linear Shrinkage Estimator ##################################################
test_that("linear shrinkage estimator with no shrinkage is S_n", {
  expect_identical(
    linearShrinkEst(mtcars, alpha = 1),
    coop::covar(mtcars)
  )
})

test_that("linear shrinkage estimator with 100% shrinkage is identity", {
  expect_identical(
    linearShrinkEst(mtcars, alpha = 0) %>% unname(),
    diag(ncol(mtcars))
  )
})

test_that("Linear shrinkage estimator produces shrunken estimates", {

  # aAl absolute elements of the estimate should be smaller than the
  # absolute value of the entries in the sample covariance matrix estimate
  dat <- scale(mtcars, center = TRUE, scale = TRUE)
  abs_est <- abs(linearShrinkEst(dat, alpha = 0.1))
  abs_sample_cov <- abs(cov(dat))
  expect_true(
    sum(round((abs_sample_cov - abs_est), digits = 6) >= 0) == 121
  )
})

test_that("Linear shrinkage estimator centers data internally", {
  expect_equal(
    linearShrinkEst(mtcars, 1),
    linearShrinkEst(mtcars - 1, 1)
  )
})

# Ledoit Wolf Linear Shrinkage Estimator #######################################
test_that("LW LS estimator runs without issue", {
  expect_silent(
    linearShrinkLWEst(mtcars)
  )
})

test_that("LW LS estimator centers data internally", {
  expect_equal(
    linearShrinkLWEst(mtcars),
    linearShrinkLWEst(mtcars - 1)
  )
})

# Simple Thresholding Estimator ###############################################
test_that("simple thresholing estimator without thresholding is S_n", {
  expect_identical(
    thresholdingEst(mtcars, gamma = 0),
    coop::covar(mtcars)
  )
})

test_that("simple thresholing estimator with large threshold is 0 matrix", {
  expect_identical(
    thresholdingEst(mtcars, gamma = 1000000) %>% unname(),
    matrix(data = 0, nrow = ncol(mtcars), ncol = ncol(mtcars))
  )
})

test_that("simple thresholding estimator centers data internally", {
  expect_equal(
    thresholdingEst(mtcars, 0.2),
    thresholdingEst(mtcars - 1, 0.2)
  )
})

# Banding Estimator ###########################################################
test_that("banding estimator with k = 0 is diagonal of S_n", {
  expect_identical(
    bandingEst(mtcars, k = 0L) %>% unname(),
    diag(diag(coop::covar(mtcars)))
  )
})

test_that("banding estimator with k >> 0 is S_n", {
  expect_identical(
    bandingEst(mtcars, k = 1000000L),
    coop::covar(mtcars) %>% unname()
  )
})

test_that("banding estimator centers data internally", {
  expect_equal(
    bandingEst(mtcars, 4),
    bandingEst(mtcars - 1, 4)
  )
})

# Tapering Estimator ##########################################################
test_that("tapering estimator with k = 0 is diagonal of S_n", {
  expect_identical(
    taperingEst(mtcars, k = 0L) %>% unname(),
    diag(diag(coop::covar(mtcars)))
  )
})

test_that("tapering estimator with k = 2*p-2 is S_n", {
  expect_identical(
    taperingEst(mtcars, k = 20L) %>% unname(),
    coop::covar(mtcars) %>% unname()
  )
})

test_that("tapering estimator with k = 2*p-4 is not S_n", {
  expect_false(identical(
    taperingEst(mtcars, k = 18L) %>% unname(),
    coop::covar(mtcars) %>% unname()
  ))
})

test_that("tapering estimator with k >> 0 is S_n", {
  expect_identical(
    taperingEst(mtcars, k = 1000000L) %>% unname(),
    coop::covar(mtcars) %>% unname()
  )
})

test_that("tapering estimator centers data internally", {
  expect_equal(
    taperingEst(mtcars, 4),
    taperingEst(mtcars - 1, 4)
  )
})

# Ledoit Wolf Nonlinear Shrinkage Estimator ####################################
test_that("LW NLS estimator runs without issue", {
  expect_silent(
    nlShrinkLWEst(mtcars)
  )

  # make sure that nlShrinkLWEst can handle case where n = p

  library(MASS)
  set.seed(123)
  Sigma <- matrix(0.5, nrow = 50, ncol = 50) + diag(0.5, nrow = 50)
  dat <- mvrnorm(n = 50, mu = rep(0, 50), Sigma = Sigma)
  expect_false(
    any(is.na(nlShrinkLWEst(dat)))
  )
})

# Dense linear Shrinkage Estimator ####################################
test_that("Dense linear shrinkage estimator runs without issue", {
  expect_silent(
    denseLinearShrinkEst(mtcars)
  )
})

test_that("Dense linear shrinkage estimator produces shrunken estimates", {

  # Mean covariance is -0.05 in sample covarian matrix.
  # All absolute covariance values are larger than 0.057.
  # Estimator should therefore produce smaller estimates in all off diagonal
  # entry (given scaling).
  dat <- scale(mtcars, center = TRUE, scale = TRUE)
  abs_est <- abs(denseLinearShrinkEst(dat))
  abs_sample_cov <- abs(cov(dat))
  expect_equal(
    sum(round((abs_sample_cov - abs_est), digits = 6) >= 0), 121
  )
  expect_equal(diag(abs_sample_cov), diag(abs_est))
  expect_true(abs_est[2, 5] != abs_sample_cov[2, 5])
})

test_that("dense linear shrinkage estimator centers data internally", {
  expect_equal(
    denseLinearShrinkEst(mtcars),
    denseLinearShrinkEst(mtcars - 1)
  )
})

# SCAD Thresholding Estimator ##################################################

test_that("SCAD estimator doesn't generate any errors for no reason", {
  expect_silent(
    scadEst(mtcars, lambda = 0.1)
  )
})

test_that("SCAD estimator generates zero matrix for large lambda", {
  dat <- scale(mtcars, center = TRUE, scale = TRUE)
  expect_equal(
    sum(scadEst(dat, lambda = 10) == 0),
    121
  )
})

test_that("SCAD estimator centers data internally", {
  expect_equal(
    scadEst(mtcars, 0.2),
    scadEst(mtcars - 1, 0.2)
  )
})

# POET Estimator ###############################################################

test_that("Verify POET estimator's results", {
  # check that the off diagonal elemets of the POET estimator equal to the
  # off diagonal elements of the rank-1 approximation of the sample covariance
  # matrix

  # compute the rank-1 approx
  dat <- scale(mtcars, center = TRUE, scale = TRUE)
  sample_cov_mat <- coop::covar(dat)
  eig_decomp <- RSpectra::eigs_sym(sample_cov_mat, 1)
  rank_one_sample_cov <- eig_decomp$values *
    eig_decomp$vectors %*% t(eig_decomp$vectors)

  # compute the POET estimate with a large lambda
  poet_estimate <- poetEst(dat, k = 1, lambda = 10)

  # remove the diagonal
  diag(rank_one_sample_cov) <- 0
  diag(poet_estimate) <- 0
  poet_estimate <- poet_estimate %>% unname()

  # compare
  expect_identical(round(rank_one_sample_cov, 10), round(poet_estimate, 10))
})

test_that("POET estimator centers data internally", {
  expect_equal(
    poetEst(mtcars, 2, 3),
    poetEst(mtcars - 1, 2, 3)
  )
})

# Robust POET Estimator ########################################################

test_that("Verify Robust POET estimator's ranks", {
  # check that the rank of robust POET equal to k when lambda is large
  # compute the robust POET estimate with a large lambda
  dat <- scale(mtcars, center = TRUE, scale = TRUE)
  k <- ceiling(ncol(dat) / 5)
  robust_poet_estimate <- robustPoetEst(dat, k,
    lambda = 10,
    var_est = "sample"
  )

  # compare
  library(Matrix)
  expect_equal(Matrix::rankMatrix(robust_poet_estimate)[1], k)
})


test_that("Verify Robust POET estimator's results", {
  # In this specific example, robust POET estimator is supposed to return a
  # matrix of all 13 or all 19.78292 dependent on the variance estimation method
  Y <- matrix(c(1:12, 2:13, 3:14, 4:15, 5:16), 12, 5)
  est <- matrix(rep(13L, 25), 5, 5)
  est_mad <- matrix(rep((3 * 1.4826)**2, 25), 5, 5)
  robust_poet_estimate <- robustPoetEst(Y, 1,
    lambda = 10,
    var_est = "sample"
  )
  robust_poet_estimate_mad <- robustPoetEst(Y, 1,
    lambda = 10,
    var_est = "mad"
  )
  # compare
  expect_equal(est, robust_poet_estimate)
  expect_equal(est_mad, robust_poet_estimate_mad)
})

test_that("robust POET estimator centers data internally", {
  expect_equal(
    robustPoetEst(mtcars, 2, 3, "sample"),
    robustPoetEst(mtcars - 1, 2, 3, "sample")
  )
})

# Adaptive Lasso Estimator #####################################################

test_that("adaptive Lasso estimator with no penalty is Sn", {
  expect_identical(
    adaptiveLassoEst(mtcars, lambda = 0, n = 0) %>% unname(),
    coop::covar(mtcars) %>% unname()
  )
})

test_that("adaptive Lasso estimator with no penalty and non-zero n is Sn", {
  expect_identical(
    adaptiveLassoEst(mtcars, lambda = 0, n = 1) %>% unname(),
    coop::covar(mtcars) %>% unname()
  )
})

test_that("adaptive Lasso estimator with large threshold is 0 matrix", {
  expect_identical(
    adaptiveLassoEst(mtcars, lambda = 1000000, n = 0) %>% unname(),
    matrix(data = 0, nrow = ncol(mtcars), ncol = ncol(mtcars))
  )
})

test_that("adaptive LASSO estimator centers data internally", {
  expect_equal(
    adaptiveLassoEst(mtcars, 0.2, 0.8),
    adaptiveLassoEst(mtcars - 1, 0.2, 0.8)
  )
})

# Operator Norm Shrinkage Estimator, Spiked Covariance Model ###################

test_that(paste("returns the sample covariance matrix when the number of",
                "spikes is zero"), {
  library(MASS)
  set.seed(123)
  dat <- mvrnorm(n = 100, mu = rep(0, 10), Sigma = diag(1, nrow = 10))
  expect_equal(spikedOperatorShrinkEst(dat, p_n_ratio = 0.1), sampleCovEst(dat))
})

test_that(paste("estimated eigenvalues are approximately equal to population",
                "eigenvalues in spike model with one spike"), {
  library(MASS)
  set.seed(62342)
  eig_vals <- c(10, rep(0.5, 9))
  dat <- mvrnorm(n = 100, mu = rep(0, 10), Sigma = diag(eig_vals, nrow = 10))
  expect_equal(eigen(spikedOperatorShrinkEst(dat, p_n_ratio = 0.1))$values,
               eig_vals, tolerance = 0.5)
})

test_that(paste("noise terms are exactly equal to corresponding eigenvalues of",
                "estimate when specified"), {
  library(MASS)
  set.seed(1231)
  eig_vals <- c(10, rep(0.5, 9))
  dat <- mvrnorm(n = 100, mu = rep(0, 10), Sigma = diag(eig_vals, nrow = 10))
  estimated_eig_vals <- eigen(
    spikedOperatorShrinkEst(dat, p_n_ratio = 0.1, noise = 0.5)
  )$values
  expect_equal(estimated_eig_vals[2:10], eig_vals[2:10])
})

test_that(paste("number of spikes in estimates equals to num_spikes when, even",
                "if specified number is wrong"), {
  library(MASS)
  set.seed(1231)
  eig_vals <- c(50, 45, 40, rep(1, 7))
  dat <- mvrnorm(n = 100, mu = rep(0, 10), Sigma = diag(eig_vals, nrow = 10))
  estimated_eig_vals <- eigen(
    spikedOperatorShrinkEst(dat, p_n_ratio = 0.1, num_spikes = 2)
  )$values
  expect_equal(sum(estimated_eig_vals > 2), 2)
})

test_that("operator norm shrinkage estimator centers data internally", {
  expect_equal(
    spikedOperatorShrinkEst(mtcars, p_n_ratio = 0.1, 2),
    spikedOperatorShrinkEst(mtcars - 1, p_n_ratio = 0.1, 2)
  )
})

# Frobenius Norm Shrinkage Estimator, Spiked Covariance Model ##################

test_that(paste("returns the sample covariance matrix when the number of",
                "spikes is zero"), {
  library(MASS)
  set.seed(123)
  dat <- mvrnorm(n = 100, mu = rep(0, 10), Sigma = diag(1, nrow = 10))
  expect_equal(spikedFrobeniusShrinkEst(dat, p_n_ratio = 0.1),
               sampleCovEst(dat))
})

test_that(paste("estimated eigenvalues are approximately equal to population",
                "eigenvalues in spike model with one spike"), {
  library(MASS)
  set.seed(62342)
  eig_vals <- c(10, rep(0.5, 9))
  dat <- mvrnorm(n = 100, mu = rep(0, 10), Sigma = diag(eig_vals, nrow = 10))
  expect_equal(eigen(spikedFrobeniusShrinkEst(dat, p_n_ratio = 0.1))$values,
               eig_vals, tolerance = 0.5)
})

test_that(paste("noise terms are exactly equal to corresponding eigenvalues of",
                "estimate when specified"), {
  library(MASS)
  set.seed(1231)
  eig_vals <- c(10, rep(0.5, 9))
  dat <- mvrnorm(n = 100, mu = rep(0, 10), Sigma = diag(eig_vals, nrow = 10))
  estimated_eig_vals <- eigen(
    spikedFrobeniusShrinkEst(dat, p_n_ratio = 0.1, noise = 0.5)
  )$values
  expect_equal(estimated_eig_vals[2:10], eig_vals[2:10])
})

test_that(paste("number of spikes in estimates equals to num_spikes when, even",
                "if specified number is wrong"), {
  library(MASS)
  set.seed(1231)
  eig_vals <- c(50, 45, 40, rep(1, 7))
  dat <- mvrnorm(n = 100, mu = rep(0, 10), Sigma = diag(eig_vals, nrow = 10))
  estimated_eig_vals <- eigen(
    spikedFrobeniusShrinkEst(dat, p_n_ratio = 0.1, num_spikes = 2)
  )$values
  expect_equal(sum(estimated_eig_vals > 2), 2)
})

test_that("Frobenius norm shrinkage estimator centers data internally", {
  expect_equal(
    spikedFrobeniusShrinkEst(mtcars, p_n_ratio = 0.1, 2),
    spikedFrobeniusShrinkEst(mtcars - 1, p_n_ratio = 0.1, 2)
  )
})

# Stein Loss Shrinkage Estimator, Spiked Covariance Model ##################

test_that(paste("returns the sample covariance matrix when the number of",
                "spikes is zero"), {
  library(MASS)
  set.seed(123)
  dat <- mvrnorm(n = 100, mu = rep(0, 10), Sigma = diag(1, nrow = 10))
  expect_equal(spikedSteinShrinkEst(dat, p_n_ratio = 0.1),
               sampleCovEst(dat))
})

test_that(paste("estimated eigenvalues are approximately equal to population",
                "eigenvalues in spike model with one spike"), {
  library(MASS)
  set.seed(62342)
  eig_vals <- c(10, rep(0.5, 9))
  dat <- mvrnorm(n = 100, mu = rep(0, 10), Sigma = diag(eig_vals, nrow = 10))
  expect_equal(eigen(spikedSteinShrinkEst(dat, p_n_ratio = 0.1))$values,
               eig_vals, tolerance = 0.5)
})

test_that(paste("noise terms are exactly equal to corresponding eigenvalues of",
                "estimate when specified"), {
  library(MASS)
  set.seed(1231)
  eig_vals <- c(10, rep(0.5, 9))
  dat <- mvrnorm(n = 100, mu = rep(0, 10), Sigma = diag(eig_vals, nrow = 10))
  estimated_eig_vals <- eigen(
    spikedSteinShrinkEst(dat, p_n_ratio = 0.1, noise = 0.5)
  )$values
  expect_equal(estimated_eig_vals[2:10], eig_vals[2:10])
})

test_that(paste("number of spikes in estimates equals to num_spikes when, even",
                "if specified number is wrong"), {
  library(MASS)
  set.seed(1231)
  eig_vals <- c(50, 45, 40, rep(1, 7))
  dat <- mvrnorm(n = 100, mu = rep(0, 10), Sigma = diag(eig_vals, nrow = 10))
  estimated_eig_vals <- eigen(
    spikedSteinShrinkEst(dat, p_n_ratio = 0.1, num_spikes = 2)
  )$values
  expect_equal(sum(estimated_eig_vals > 2), 2)
})

test_that("Stein loss norm shrinkage estimator centers data internally", {
  expect_equal(
    spikedSteinShrinkEst(mtcars, p_n_ratio = 0.1, 2),
    spikedSteinShrinkEst(mtcars - 1, p_n_ratio = 0.1, 2)
  )
})
