# Copyright (c) 2020-2023 Nuno Fachada
# Distributed under the MIT License (http://opensource.org/licenses/MIT)

# ################################## #
# Test clugen() mandatory parameters #
# ################################## #

# Create parameter combinations to test
targs <- expand.grid(seed = seeds, nd = num_dims,
                     nclu = num_clusters,
                     tpts = num_points,
                     astd = angles_stds,
                     len = llengths_mus,
                     len_std = llengths_sigmas,
                     lat_std = lat_stds)

# Loop through all parameter combinations
for (i in seq.int(1, nrow(targs))) {

  # Get current parameters
  seed <- targs[i, "seed"]
  nd <- targs[i, "nd"]
  nclu <- targs[i, "nclu"]
  tpts <- targs[i, "tpts"]
  astd <- targs[i, "astd"]
  len <- targs[i, "len"]
  len_std <- targs[i, "len_std"]
  lat_std <- targs[i, "lat_std"]

  # Set seed
  set.seed(seed)

  # Create combination of seed-depending parameters (line directions + clu seps)
  tsargs <- expand.grid(direc = append(asplit(get_vecs(ndirs, nd), 1),
                                       list(matrix(rnorm(nclu * nd),
                                                   ncol = nd))),
                        clusep = asplit(get_clu_seps(nd), 1))

  # Loop through line directions and line centers
  for (j in seq.int(1, nrow(tsargs))) {

    # Get current line direction + line center
    direc <- tsargs[j, "direc"][[1]]
    clusep <- tsargs[j, "clusep"][[1]]

    # Determine test name for current parameter set
    test_desc <- paste0("clugen mandatory params: ",
                        "seed=", seed, ", nd=", nd, ", nclu=", nclu,
                        ", tpts=", tpts, ", astd=", astd, ", len=", len,
                        ", len_std=", len_std, ", lat_std=", lat_std,
                        ", direc=[", paste(direc, collapse = ", "),
                        "], clusep=[", paste(clusep, collapse = ", "), "]")

    # Perform tests for current parameter set
    test_that(test_desc, {

      # By default, allow_empty is false, so clugen() must be given more points
      # than clusters...
      if (tpts >= nclu) {
        # ...in which case it runs without problem
        expect_warning(r <- clugen(nd, nclu, tpts, direc, astd,
                                   clusep, len, len_std, lat_std),
                       regexp = NA)

        # Check dimensions of result variables
        expect_equal(dim(r$points), c(tpts, nd))
        expect_equal(length(r$clusters), tpts)
        expect_equal(dim(r$projections), c(tpts, nd))
        expect_equal(length(r$sizes), nclu)
        expect_equal(dim(r$centers), c(nclu, nd))
        expect_equal(dim(r$directions), c(nclu, nd))
        expect_equal(length(r$angles), nclu)
        expect_equal(length(r$lengths), nclu)

        # Check point cluster indexes
        expect_equal(unique(as.numeric(r$clusters)), 1:nclu)

        # Check total points
        expect_equal(sum(r$sizes), tpts)

        # Check that cluster directions have the correct angles with the main
        # direction
        if (nd > 1) {
          # In case direction is just a vector, repeat it num_cluster times
          # into a matrix...
          if (is.vector(direc) ||
              (is.array(direc) && length(dim(direc)) == 1)) {
            direc <- matrix(direc,
                            nrow = nclu,
                            ncol = length(direc),
                            byrow = TRUE)
          }
          # ...so we can check each cluster direction separately
          for (i in 1:nclu) {
            expect_equal(angle_btw(direc[i, ], r$directions[i, ]),
                         abs(r$angles[i]))
          }
        }

      } else {
        # ...otherwise an error will be thrown
        expect_error(r <- clugen(nd, nclu, tpts, direc, astd,
                                 clusep, len, len_std, lat_std),
                     paste("A total of", tpts, "points is not enough for", nclu,
                           "non-empty clusters"))
      }
    })
  }
}

# ################################# #
# Test clugen() optional parameters #
# ################################# #

# Valid arguments
nclu <- 7
tpts <- 500
astd <- pi / 256
len_mu <- 9
len_std <- 1.2
lat_std <- 2

# Alternative functions used for testing
prjdist_equi <- function(l, n) seq(from = -l / 2, to = l / 2, length.out = n)
ptdist_projs_plus_1 <- function(projs, ls, l, cd, cc) projs + 1
csz_equi <- function(nclu, tpts, ae) {
  cs <- vector(mode = "integer", length = nclu)
  for (i in 1:tpts) {
    cs[i %% nclu + 1] <- cs[i %% nclu + 1] + 1
  }
  cs
}
cctr_on_a_line <- function(nclu, cs, co) {
  1:nclu * matrix(0, nrow = nclu, ncol = length(cs))
}
llen_unif_10_20 <- function(nclu, l, lsd) stats::runif(nclu, min = 10, max = 20)
lang_same <- function(nclu, astd) vector(mode = "double", length = nclu)

# Create parameter combinations to test
if (testthat:::on_cran() || is_test_mode("cran")) {
  # Light tests, for CRAN
  targs <- expand.grid(seed = seeds, nd = 2, ae = TRUE,
                       prjdist_fn = list("norm", "unif"),
                       ptdist_fn = list("n-1", "n"),
                       csz_fn = list(clusizes),
                       cctr_fn = list(clucenters),
                       llen_fn = list(llengths),
                       lang_fn = list(angle_deltas))
} else {
  # Other testing modes will be more thorough
  targs <- expand.grid(seed = seeds, nd = c(2, 7), ae = allow_empties,
                       prjdist_fn = list("norm", "unif", prjdist_equi),
                       ptdist_fn = list("n-1", "n", ptdist_projs_plus_1),
                       csz_fn = list(clusizes, csz_equi),
                       cctr_fn = list(clucenters, cctr_on_a_line),
                       llen_fn = list(llengths, llen_unif_10_20),
                       lang_fn = list(angle_deltas, lang_same))
}

# Loop through all parameter combinations
for (i in seq.int(1, nrow(targs))) {

  # Get current parameters
  seed <- targs[i, "seed"]
  nd <- targs[i, "nd"]
  ae <- targs[i, "ae"]
  prjdist_fn <- targs[i, "prjdist_fn"][[1]]
  ptdist_fn <- targs[i, "ptdist_fn"][[1]]
  csz_fn <- targs[i, "csz_fn"][[1]]
  cctr_fn <- targs[i, "cctr_fn"][[1]]
  llen_fn <- targs[i, "llen_fn"][[1]]
  lang_fn <- targs[i, "lang_fn"][[1]]

  # Set seed
  set.seed(seed)

  # Create combination of seed-depending parameters (line directions + clu seps)
  tsargs <- expand.grid(direc = asplit(get_vecs(ndirs, nd), 1),
                        clusep = asplit(get_clu_seps(nd), 1))

  # Loop through line directions and line centers
  for (j in seq.int(1, nrow(tsargs))) {

    # Get current line direction + line center
    direc <- tsargs[j, "direc"][[1]]
    clusep <- tsargs[j, "clusep"][[1]]

    # Determine test name for current parameter set
    test_desc <- paste0("clugen optional params: ",
                        "seed = ", seed, ", nd=", nd, ", nclu=", nclu,
                        ", tpts=", tpts, "ae=", ae,
                        ", ptdist_fn='",
                        format(prjdist_fn)[length(format(prjdist_fn))],
                        "', ptoff_fn='",
                        format(ptdist_fn)[length(format(ptdist_fn))],
                        "', czn_fn='",
                        format(csz_fn)[length(format(csz_fn))],
                        "'",
                        "', cctr_fn='",
                        format(cctr_fn)[length(format(cctr_fn))],
                        "'",
                        "', llen_fn='",
                        format(llen_fn)[length(format(llen_fn))],
                        "'",
                        "', lang_fn='",
                        format(lang_fn)[length(format(lang_fn))],
                        "'")

    # Perform tests for current parameter set
    test_that(test_desc, {

      # ...in which case it runs without problem
      expect_warning(r <- clugen(nd, nclu, tpts, direc, astd,
                                 clusep, len_mu, len_std, lat_std,
                                 allow_empty = ae,
                                 proj_dist_fn = prjdist_fn,
                                 point_dist_fn = ptdist_fn,
                                 clusizes_fn = csz_fn,
                                 clucenters_fn = cctr_fn,
                                 llengths_fn = llen_fn,
                                 angle_deltas_fn = lang_fn,
                                 seed = seed),
                     regexp = NA)

      # Check dimensions of result variables
      expect_equal(dim(r$points), c(tpts, nd))
      expect_equal(length(r$clusters), tpts)
      expect_equal(dim(r$projections), c(tpts, nd))
      expect_equal(length(r$sizes), nclu)
      expect_equal(dim(r$centers), c(nclu, nd))
      expect_equal(dim(r$directions), c(nclu, nd))
      expect_equal(length(r$angles), nclu)
      expect_equal(length(r$lengths), nclu)

      # Check point cluster indexes
      if (!ae) {
        expect_equal(unique(as.numeric(r$clusters)), 1:nclu)
      } else {
        expect_true(all(as.numeric(r$clusters) <= nclu))
      }

      # Check total points
      expect_equal(sum(r$sizes), tpts)
      # This might not be the case if the specified clusize_fn does not obey
      # the total number of points

      # Check that cluster directions have the correct angles with the main
      # direction
      if (nd > 1) {
        for (i in 1:nclu) {
          expect_equal(angle_btw(direc, r$directions[i, ]),
                       abs(r$angles[i]))
        }
      }
    })
  }
}

# ######################################## #
# Test clugen() optional direct parameters #
# ######################################## #

# Valid arguments
astd <- pi / 333
len_mu <- 6
len_std <- 1.1
lat_std <- 1.6

# Create parameter combinations to test
if (testthat:::on_cran() || is_test_mode("cran")) {
  # Light tests, for CRAN
  targs <- expand.grid(seed = seeds, nd = 2, nclu = 6)
} else {
  # Other testing modes will be more thorough
  targs <- expand.grid(seed = seeds, nd = c(1, 5), nclu = c(1, 6))
}

# Loop through all parameter combinations
for (i in seq.int(1, nrow(targs))) {

  # Get current parameters
  seed <- targs[i, "seed"]
  nd <- targs[i, "nd"]
  nclu <- targs[i, "nclu"]

  # Set seed
  set.seed(seed)

  # Create combination of seed-depending parameters
  direc <- rnorm(nd)
  clusep <- 100 * rnorm(nd)
  csz_direct <- sample(1:100, nclu, replace = TRUE)
  cctr_direct <- matrix(100 * rnorm(nd * nclu), nrow = nclu)
  llen_direct <- runif(nclu, min = 0, max = 100)
  lang_direct <- runif(nclu, min = -pi / 2, max = pi / 2)
  tpts <- sum(csz_direct)

  # Determine test name for current parameter set
  test_desc <- paste0("clugen optional params (direct): ",
                      "seed = ", seed, ", nd=", nd, ", nclu=", nclu,
                      ", direc=[", paste(direc, collapse = ", "),
                      ", clusep=[", paste(clusep, collapse = ", "),
                      ", csz_direct=[", paste(csz_direct, collapse = ", "),
                      ", cctr_direct=[", paste(cctr_direct, collapse = ", "),
                      ", llen_direct=[", paste(llen_direct, collapse = ", "),
                      ", lang_direct=[", paste(lang_direct, collapse = ", "))

  # Perform tests for current parameter set
  test_that(test_desc, {

    # ...in which case it runs without problem
    expect_warning(r <- clugen(nd, nclu, tpts, direc, astd,
                               clusep, len_mu, len_std, lat_std,
                               allow_empty = ae,
                               clusizes_fn = csz_direct,
                               clucenters_fn = cctr_direct,
                               llengths_fn = llen_direct,
                               angle_deltas_fn = lang_direct,
                               seed = seed),
                   regexp = NA)

    # Check dimensions of result variables
    expect_equal(dim(r$points), c(tpts, nd))
    expect_equal(length(r$clusters), tpts)
    expect_equal(dim(r$projections), c(tpts, nd))
    expect_equal(length(r$sizes), nclu)
    expect_equal(dim(r$centers), c(nclu, nd))
    expect_equal(dim(r$directions), c(nclu, nd))
    expect_equal(length(r$angles), nclu)
    expect_equal(length(r$lengths), nclu)

    # Check point cluster indexes
    if (!ae) {
      expect_equal(unique(as.numeric(r$clusters)), 1:nclu)
    } else {
      expect_true(all(as.numeric(r$clusters) <= nclu))
    }

    # Check total points
    expect_equal(sum(r$sizes), tpts)
    # This might not be the case if the specified clusize_fn does not obey
    # the total number of points

    # Check that cluster directions have the correct angles with the main
    # direction
    if (nd > 1) {
      for (i in 1:nclu) {
        expect_equal(angle_btw(direc, r$directions[i, ]),
                     abs(r$angles[i]))
      }
    }
  })
}

# ##################### #
# Reproducibility tests #
# ##################### #

# Valid parameters
nd <- 2
nclu <- 4
tpts <- 300
direc <- c(1, 1)
astd <- pi / 64
clusep <- c(7, 6.5)
len_mu <- 4.1
len_std <- 0.5
lat_std <- 0.2

for (seed in seeds) {

  test_that(paste0("clugen reproducibility: seed=", seed), {

    # Get results for run 1 with current seed
    expect_warning(r1 <- clugen(nd, nclu, tpts, direc, astd, clusep,
                                len_mu, len_std, lat_std,
                                seed = seed),
                   regexp = NA)

    # Get results for run 2 with current seed
    expect_warning(r2 <- clugen(nd, nclu, tpts, direc, astd, clusep,
                                len_mu, len_std, lat_std,
                                seed = seed),
                   regexp = NA)

    # Check that results are exactly the same
    expect_equal(r1, r2)
  })
}

# ######################## #
# Test clugen() exceptions #
# ######################## #

for (seed in seeds) {

  # Valid parameters
  nd <- 3
  nclu <- 5
  tpts <- 1000
  direc <- c(1, 0, 0)
  astd <- pi / 64
  clusep <- c(10, 10, 5)
  len_mu <- 5
  len_std <- 0.5
  lat_std <- 0.3
  ae <- TRUE
  clu_off <- c(-1.5, 0, 2)
  prj_dist <- "unif"
  pt_dist <- "n-1"
  csizes_fn <- clusizes
  ccenters_fn <- clucenters
  llengths_fn <- llengths
  langles_fn <- angle_deltas

  test_that(paste0("clugen exceptions: seed=", seed), {

    # Test passes with valid arguments
    expect_warning(r <- clugen(nd, nclu, tpts, direc, astd, clusep,
                               len_mu, len_std, lat_std,
                               allow_empty = ae,
                               cluster_offset = clu_off,
                               proj_dist_fn = prj_dist,
                               point_dist_fn = pt_dist,
                               clusizes_fn = csizes_fn,
                               clucenters_fn = ccenters_fn,
                               llengths_fn = llengths_fn,
                               angle_deltas_fn = langles_fn,
                               seed = seed),
                   regexp = NA)

    # Test passes with zero points since allow_empty is set to true
    expect_warning(r <- clugen(nd, nclu, 0, direc, astd, clusep,
                               len_mu, len_std, lat_std,
                               allow_empty = ae,
                               cluster_offset = clu_off,
                               proj_dist_fn = prj_dist,
                               point_dist_fn = pt_dist,
                               clusizes_fn = csizes_fn,
                               clucenters_fn = ccenters_fn,
                               llengths_fn = llengths_fn,
                               angle_deltas_fn = langles_fn,
                               seed = seed),
                   regexp = NA)

    # Invalid number of dimensions
    expect_error(r <- clugen(0, nclu, tpts, direc, astd, clusep,
                             len_mu, len_std, lat_std,
                             allow_empty = ae,
                             cluster_offset = clu_off,
                             proj_dist_fn = prj_dist,
                             point_dist_fn = pt_dist,
                             clusizes_fn = csizes_fn,
                             clucenters_fn = ccenters_fn,
                             llengths_fn = llengths_fn,
                             angle_deltas_fn = langles_fn,
                             seed = seed),
                 regexp = "Number of dimensions, `num_dims`, must be > 0",
                 fixed = TRUE)

    # Invalid number of clusters
    expect_error(r <- clugen(nd, 0, tpts, direc, astd, clusep,
                             len_mu, len_std, lat_std,
                             allow_empty = ae,
                             cluster_offset = clu_off,
                             proj_dist_fn = prj_dist,
                             point_dist_fn = pt_dist,
                             clusizes_fn = csizes_fn,
                             clucenters_fn = ccenters_fn,
                             llengths_fn = llengths_fn,
                             angle_deltas_fn = langles_fn,
                             seed = seed),
                 regexp = "Number of clusters, `num_clust`, must be > 0",
                 fixed = TRUE)

    # Direction needs to have magnitude > 0
    expect_error(r <- clugen(nd, nclu, tpts, c(0, 0, 0), astd, clusep,
                             len_mu, len_std, lat_std,
                             allow_empty = ae,
                             cluster_offset = clu_off,
                             proj_dist_fn = prj_dist,
                             point_dist_fn = pt_dist,
                             clusizes_fn = csizes_fn,
                             clucenters_fn = ccenters_fn,
                             llengths_fn = llengths_fn,
                             angle_deltas_fn = langles_fn,
                             seed = seed),
                 regexp = "`direction` must have magnitude > 0",
                 fixed = TRUE)

    # Direction needs to have nd dims
    bad_dir <- c(1, 1)
    expect_error(r <- clugen(nd, nclu, tpts, bad_dir, astd, clusep,
                             len_mu, len_std, lat_std,
                             allow_empty = ae,
                             cluster_offset = clu_off,
                             proj_dist_fn = prj_dist,
                             point_dist_fn = pt_dist,
                             clusizes_fn = csizes_fn,
                             clucenters_fn = ccenters_fn,
                             llengths_fn = llengths_fn,
                             angle_deltas_fn = langles_fn,
                             seed = seed),
                 regexp = paste0(
                   "Length of directions in `direction` must be equal to ",
                   "`num_dims` (", length(bad_dir), " != ", nd, ")"),
                 fixed = TRUE)

    # Specific direction for each cluster requires one direction per cluster
    expect_error(r <- clugen(nd, nclu, tpts,
                             # but we're passing one extra direction
                             matrix(rnorm(nd * (nclu + 1)), ncol = nd),
                             astd, clusep,
                             len_mu, len_std, lat_std,
                             allow_empty = ae,
                             cluster_offset = clu_off,
                             proj_dist_fn = prj_dist,
                             point_dist_fn = pt_dist,
                             clusizes_fn = csizes_fn,
                             clucenters_fn = ccenters_fn,
                             llengths_fn = llengths_fn,
                             angle_deltas_fn = langles_fn,
                             seed = seed),
                 regexp = paste0("Number of rows in `direction` must be the ",
                                 "same as the number of clusters (",
                                 nclu + 1, " != ", nclu, ")"),
                 fixed = TRUE)

    # Direction needs to be a 1D array (vector) or 2D array (matrix)
    expect_error(r <- clugen(nd, nclu, tpts,
                               # but we're passing a 3D array
                               array(rnorm(nd * nclu * 2), c(nd, nclu, 2)),
                               astd, clusep, len_mu, len_std, lat_std,
                               allow_empty = ae,
                               cluster_offset = clu_off,
                               proj_dist_fn = prj_dist,
                               point_dist_fn = pt_dist,
                               clusizes_fn = csizes_fn,
                               clucenters_fn = ccenters_fn,
                               llengths_fn = llengths_fn,
                               angle_deltas_fn = langles_fn,
                               seed = seed),
                   regexp = paste0("`direction` must be a vector (1D array) ",
                                   "or a matrix (2D array)"),
                   fixed = TRUE)

    # cluster_sep needs to have nd dims
    bad_clusep <- c(10, 10)
    expect_error(r <- clugen(nd, nclu, tpts, direc, astd,
                             bad_clusep,
                             len_mu, len_std, lat_std,
                             allow_empty = ae,
                             cluster_offset = clu_off,
                             proj_dist_fn = prj_dist,
                             point_dist_fn = pt_dist,
                             clusizes_fn = csizes_fn,
                             clucenters_fn = ccenters_fn,
                             llengths_fn = llengths_fn,
                             angle_deltas_fn = langles_fn,
                             seed = seed),
                 regexp = paste0(
                   "Length of `cluster_sep` must be equal to `num_dims` (",
                   length(bad_clusep), " != ", nd, ")"),
                 fixed = TRUE)

    # cluster_offset needs to have nd dims
    bad_cluoff <- c(0, 1)
    expect_error(r <- clugen(nd, nclu, tpts, direc, astd, clusep,
                             len_mu, len_std, lat_std,
                             allow_empty = ae,
                             cluster_offset = bad_cluoff,
                             proj_dist_fn = prj_dist,
                             point_dist_fn = pt_dist,
                             clusizes_fn = csizes_fn,
                             clucenters_fn = ccenters_fn,
                             llengths_fn = llengths_fn,
                             angle_deltas_fn = langles_fn,
                             seed = seed),
                 regexp = paste0("Length of `cluster_offset` must be equal to ",
                                 "`num_dims` (",
                                 length(bad_cluoff), " != ", nd, ")"),
                 fixed = TRUE)

    # Unknown proj_dist_fn given as string
    expect_error(r <- clugen(nd, nclu, tpts, direc, astd, clusep,
                             len_mu, len_std, lat_std,
                             allow_empty = ae,
                             cluster_offset = clu_off,
                             proj_dist_fn = "invalid",
                             point_dist_fn = pt_dist,
                             clusizes_fn = csizes_fn,
                             clucenters_fn = ccenters_fn,
                             llengths_fn = llengths_fn,
                             angle_deltas_fn = langles_fn,
                             seed = seed),
                 regexp = paste0(
                   "`proj_dist_fn` has to be either \"norm\", \"unif\" or",
                   " user-defined function"),
                 fixed = TRUE)

    # Invalid proj_dist_fn given as function
    expect_error(r <- clugen(nd, nclu, tpts, direc, astd, clusep,
                             len_mu, len_std, lat_std,
                             allow_empty = ae,
                             cluster_offset = clu_off,
                             proj_dist_fn = csizes_fn,
                             point_dist_fn = pt_dist,
                             clusizes_fn = csizes_fn,
                             clucenters_fn = ccenters_fn,
                             llengths_fn = llengths_fn,
                             angle_deltas_fn = langles_fn,
                             seed = seed),
                 "argument")

    # Unknown point_dist_fn given as string
    expect_error(r <- clugen(nd, nclu, tpts, direc, astd, clusep,
                             len_mu, len_std, lat_std,
                             allow_empty = ae,
                             cluster_offset = clu_off,
                             proj_dist_fn = prj_dist,
                             point_dist_fn = "invalid",
                             clusizes_fn = csizes_fn,
                             clucenters_fn = ccenters_fn,
                             llengths_fn = llengths_fn,
                             angle_deltas_fn = langles_fn,
                             seed = seed),
                 regexp = paste0("point_dist_fn has to be either \"n-1\",",
                                 " \"n\" or a user-defined function"),
                 fixed = TRUE)

    # Invalid point_dist_fn given as function
    expect_error(r <- clugen(nd, nclu, tpts, direc, astd, clusep,
                             len_mu, len_std, lat_std,
                             allow_empty = ae,
                             cluster_offset = clu_off,
                             proj_dist_fn = prj_dist,
                             point_dist_fn = llengths_fn,
                             clusizes_fn = csizes_fn,
                             clucenters_fn = ccenters_fn,
                             llengths_fn = llengths_fn,
                             angle_deltas_fn = langles_fn,
                             seed = seed),
                 regexp = "argument")

    # Invalid direct clusizes
    expect_error(r <- clugen(nd, nclu, tpts, direc, astd, clusep,
                             len_mu, len_std, lat_std,
                             allow_empty = ae,
                             cluster_offset = clu_off,
                             proj_dist_fn = prj_dist,
                             point_dist_fn = pt_dist,
                             clusizes_fn = sample(1:(tpts * nclu), nclu + 1),
                             clucenters_fn = ccenters_fn,
                             llengths_fn = llengths_fn,
                             angle_deltas_fn = langles_fn,
                             seed = seed),
                 regexp = paste0("`clusizes_fn` has to be either a function or",
                                 " a `num_clusters`-sized vector"),
                 fixed = TRUE)

    # Invalid direct clucenters
    expect_error(r <- clugen(nd, nclu, tpts, direc, astd, clusep,
                             len_mu, len_std, lat_std,
                             allow_empty = ae,
                             cluster_offset = clu_off,
                             proj_dist_fn = prj_dist,
                             point_dist_fn = pt_dist,
                             clusizes_fn = csizes_fn,
                             clucenters_fn = matrix(rnorm(nd * (nclu + 1)),
                                                    nrow = nclu + 1),
                             llengths_fn = llengths_fn,
                             angle_deltas_fn = langles_fn,
                             seed = seed),
                 regexp = paste0("clucenters_fn has to be either a function or",
                                 " a matrix of size `num_clusters` x `num_dims`"),
                 fixed = TRUE)

    # Invalid direct llengths
    expect_error(r <- clugen(nd, nclu, tpts, direc, astd, clusep,
                             len_mu, len_std, lat_std,
                             allow_empty = ae,
                             cluster_offset = clu_off,
                             proj_dist_fn = prj_dist,
                             point_dist_fn = pt_dist,
                             clusizes_fn = csizes_fn,
                             clucenters_fn = ccenters_fn,
                             llengths_fn = runif(nclu + 1),
                             angle_deltas_fn = langles_fn,
                             seed = seed),
                 regexp = paste0("`llengths_fn` has to be either a function or",
                                 " a `num_clusters`-sized vector"),
                 fixed = TRUE)

    # Invalid direct langles
    expect_error(r <- clugen(nd, nclu, tpts, direc, astd, clusep,
                             len_mu, len_std, lat_std,
                             allow_empty = ae,
                             cluster_offset = clu_off,
                             proj_dist_fn = prj_dist,
                             point_dist_fn = pt_dist,
                             clusizes_fn = csizes_fn,
                             clucenters_fn = ccenters_fn,
                             llengths_fn = llengths_fn,
                             angle_deltas_fn = runif(nclu + 1),
                             seed = seed),
                 regexp = paste0("`angle_deltas_fn` has to be either a function",
                                 " or a `num_clusters`-sized vector"),
                 fixed = TRUE)

  })
}
