### NOTE: disabled snapshot testing since the direction of the order is not defined and randomized
###       for some methods.

library(seriation)
library(testthat)

extra_integer <- NULL
extra_hclust <- NULL

if(seriation:::check_installed("DendSer", "check")) {
  register_DendSer()
  extra_hclust <- append(extra_hclust, c("DendSer", "DendSer_ARc",
                                           "DendSer_BAR", "DendSer_LPL",
                                           "DendSer_PL"))
  }

if(seriation:::check_installed("umap", "check")) {
  extra_integer <- append(extra_integer, "umap")
  register_umap()
}

x <- matrix(
  c(1, 1, 0, 0, 0,
    1, 1, 1, 0, 0,
    0, 0, 1, 1, 1,
    1, 0, 1, 1, 1),
  byrow = TRUE,
  ncol = 5,
  dimnames = list(letters[1:4], LETTERS[1:5])
)

d <- dist(x)

x0 <- matrix(NA, nrow = 0, ncol = 0)
d0 <- dist(x0)

x1 <- rbind(c(1,2,3))
d1 <- dist(x1)


test_that("test if seriate.dist returns expected results", {

  if (interactive())
    cat("\n      seriate dist\n") # for cleaner testthat output

  methods <- list_seriation_methods(kind = "dist")
  ### insufficient data for metaMDS
  methods <- setdiff(methods, "metaMDS")

  os <- sapply(methods, function(m) {

    if (interactive())
      cat("   -> testing", format(m, width = 13), "... ")

    # check 0 and 1 objects
    expect_error(o <- seriate(d0, method = m))
    o <- seriate(d1, method = m)
    expect_length(0, 1L)

    # check example with timing
    tm <- system.time(o <- seriate(d, method = m))
    if (interactive())
      cat("took", formatC(tm[3], digits = 4), "s.\n")
    o
  })
  # make sure they are all the right length
  expect_true(all(sapply(os, length) == nrow(x)))

  # check which methods produce hclusts and which integers
  hclusts <- os[sapply(os, function(x)
    inherits(x, "hclust"))]
  expect_setequal(
    object = names(hclusts),
    expected = c(
      "GW",
      "GW_average",
      "GW_complete",
      "GW_single",
      "GW_ward",
      "HC",
      "HC_average",
      "HC_complete",
      "HC_single",
      "HC_ward",
      "OLO",
      "OLO_average",
      "OLO_complete",
      "OLO_single",
      "OLO_ward",
      extra_hclust
    )
  )
  integers <- os[sapply(os, is.integer)]
  expect_setequal(
    object = names(integers),
    expected = c(
      "ARSA",
      "Enumerate",
      "BBURCG",
      "BBWRCG",
      "Identity",
      "MDS",
      "MDS_angle",
      # "metaMDS",
      "monoMDS",
      "isomap",
      "isoMDS",
      "Sammon_mapping",
      "QAP_2SUM",
      "QAP_BAR",
      "QAP_Inertia",
      "QAP_LS",
      "R2E",
      "Random",
      "Reverse",
      "GSA",
      "SGD",
      "Spectral",
      "Spectral_norm",
      "SPIN_NH",
      "SPIN_STS",
      "TSP",
      "VAT",
      extra_integer
    )
  )
  expect_setequal(c(names(hclusts), names(integers)), expected = names(os))

  # check all orders are integers
  ORDERS <-
    sapply(
      X = os,
      FUN = get_order,
      dim = 1,
      simplify = FALSE
    )

  for (o in ORDERS) {
    expect_type(o, "integer")
    expect_mapequal(o, expected = c(
      a = 1,
      b = 2,
      c = 3,
      d = 4
    ))
    expect_type(names(o), "character")
  }

  # check $labels of hclust seriation vectors remain in original input order
  for (n in names(hclusts)) {
    expect_equal(hclusts[[n]][["labels"]], expected = letters[1:4])
  }

  # check names of get_order(<hclust seriation vector>) equal to ordered labels
  for (n in names(hclusts)) {
    expect_equal(object = names(ORDERS[[n]]),
      expected = hclusts[[n]][["labels"]][hclusts[[n]][["order"]]])
  }

  # check snapshot of some deterministic methods
  deterMethods <- c(
    "BBURCG",
    "BBWRCG",
    "GW",
    "GW_average",
    "GW_complete",
    "GW_single",
    "GW_ward",
    "HC",
    "HC_average",
    "HC_complete",
    "HC_single",
    "HC_ward",
    "Identity",
    "MDS",
    "isoMDS",
    "Sammon_mapping",
    # this use eigen() which gives slightly different results for OpenBLAS and M1 architecture
    "MDS_angle",
    #"R2E",
    "Spectral",
    "Spectral_norm",
    "VAT"
  )

  # recreate with dput(lapply(os[deterMethods], get_order))
  correct <-
    list(
      BBURCG = c(
        a = 1L,
        b = 2L,
        d = 4L,
        c = 3L
      ),
      BBWRCG = c(
        a = 1L,
        b = 2L,
        d = 4L,
        c = 3L
      ),
      GW = c(
        a = 1L,
        b = 2L,
        d = 4L,
        c = 3L
      ),
      GW_average = c(
        a = 1L,
        b = 2L,
        d = 4L,
        c = 3L
      ),
      GW_complete = c(
        a = 1L,
        b = 2L,
        d = 4L,
        c = 3L
      ),
      GW_single = c(
        a = 1L,
        b = 2L,
        d = 4L,
        c = 3L
      ),
      GW_ward = c(
        a = 1L,
        b = 2L,
        d = 4L,
        c = 3L
      ),
      HC = structure(1:4, names = c("a",
                                    "b", "c", "d")),
      HC_average = structure(1:4, names = c("a", "b",
                                            "c", "d")),
      HC_complete = structure(1:4, names = c("a", "b",
                                             "c", "d")),
      HC_single = structure(1:4, names = c("a", "b", "c",
                                           "d")),
      HC_ward = structure(1:4, names = c("a", "b", "c", "d")),
      Identity = structure(1:4, names = c("a", "b", "c", "d")),
      MDS = c(
        a = 1L,
        b = 2L,
        d = 4L,
        c = 3L
      ),
      isoMDS = c(
        a = 1L,
        b = 2L,
        d = 4L,
        c = 3L
      ),
      Sammon_mapping = c(
        a = 1L,
        b = 2L,
        d = 4L,
        c = 3L
      ),
       MDS_angle = c(
         a = 1L,
         b = 2L,
         d = 4L,
         c = 3L
       ),
       R2E = c(
         c = 3L,
         d = 4L,
         b = 2L,
         a = 1L
       ),
       Spectral = c(
         c = 3L,
         d = 4L,
         b = 2L,
         a = 1L
       ),
       Spectral_norm = c(c = 3L, d = 4L,
                         b = 2L, a = 1L),
      VAT = c(c = 3L, d = 4L, b = 2L, a = 1L))

  # Notes:
  # * some systems may produce the reverse order for some methods!
  # * ARM-based M1 systems produce different results for eigenvalues.
  #   This is not an error, just a numerical difference. We skip that test for now.
  #skip_on_os("mac", arch = "aarch64")

  for (m in deterMethods)
    expect_true(
      identical(correct[[m]], get_order(os[[m]])) ||
        identical(correct[[m]], rev(get_order(os[[m]]))),
      label = paste("Seriation method", m, "does not return the correct order!\n")
    )

  # make sure they are all the right length
  expect_true(all(sapply(os, length) == nrow(x)))
})

# check seriate errors for bad dist objects
test_that("test if negative distances and NAs prompt correct seriate.dist errors", {
  dNeg <- d
  dNeg[1] <- -1
  expect_error(seriate(dNeg), "Negative distances not supported")

  dNA <- d
  dNA[1] <- NA
  expect_error(seriate(dNA), "NAs not allowed in distance matrix x")
})

test_that("test if dist objects without Diag or Upper attributes can be permuted", {
  # eurodist is an object of class dist from built in R package "datasets"
  expect_s3_class(eurodist, "dist")
  expect_identical(attr(eurodist, "Diag"), NULL)
  expect_identical(attr(eurodist, "Upper"), NULL)
  s <- seriate(eurodist, method = "MDS")
  expect_s3_class(p <- permute(eurodist, order = s), "dist")
  expect_false(attr(p, "Diag")) # permutation adds Diag, is this desirable?
  expect_false(attr(p, "Upper"))
  expect_equal(labels(p), names(get_order(s)))
})

### Stress test to find memory access problems with randomized algorithms
#context("memory stress test")
#replicate(1000, seriate(d, method="bburcg"))
#replicate(1000, seriate(d, method="bbwrcg"))
#replicate(1000, seriate(d, method="arsa"))

test_that("test if seriate.matrix returns expected results", {
  #local_edition(3) # for snapshot testing

  if (interactive())
    cat("\n      seriate matrix\n") # for cleaner testthat output
  methods <- list_seriation_methods(kind = "matrix")

  ### AOE is for symmetric correlation matrices
  methods <- setdiff(methods, "AOE")

  os <- sapply(methods, function(m) {
    if (interactive())
      cat("   -> testing", format(m, width = 13), "... ")

    # check with 0 and 1 objects
    expect_error(o <- seriate(x0, method = m))

    # need at least 2x2 matrix
    if (m %in% c("PCA_angle", "umap"))
      expect_error(o <- seriate(x1, method = m))
    else
      o <- seriate(x1, method = m)

    tm <- system.time(o <- seriate(x, method = m))
    if (interactive())
      cat("took", formatC(tm[3], digits = 4), "s.\n")
    o
  }, simplify = FALSE)

  # check number and length of orders
  expect_true(all(sapply(os, length) == 2L))
  expect_true(all(sapply(
    os,
    FUN = function(o2)
      sapply(o2, length)
  ) == c(4L, 5L)))

  x_p <- permute(x, os[[1]]) # BEA method
  expect_equal(x_p, x[get_order(os[[1]], 1), get_order(os[[1]], 2)])

  # check labels
  expect_equal(get_order(os$Identity, 1), c(
    a = 1,
    b = 2,
    c = 3,
    d = 4
  ))
  expect_equal(get_order(os$Identity, 2), c(
    A = 1,
    B = 2,
    C = 3,
    D = 4,
    E = 5
  ))
  expect_equal(get_order(os$Reverse, 1), c(
    d = 4,
    c = 3,
    b = 2,
    a = 1
  ))
  expect_equal(get_order(os$Reverse, 2), c(
    E = 5,
    D = 4,
    C = 3,
    B = 2,
    A = 1
  ))

  # check snapshot of some deterministic methods
  #deterMethods <- c("CA", "Identity", "PCA", "PCA_angle", "Reverse")
  #expect_snapshot(str(os[deterMethods]))
})

test_that("test if seriate.matrix with margin returns expected results", {
  #local_edition(3) # for snapshot testing


  if (interactive())
    cat("\n     seriate matrix with margin\n") # for cleaner testthat output
  methods <- list_seriation_methods(kind = "matrix")

  ### AOE is for symmetric correlation matrices
  methods <- setdiff(methods, "AOE")

  os <- sapply(methods, function(m) {
    if (interactive())
      cat("   -> testing", format(m, width = 13), "... ")

    tm <- system.time(o <- seriate(x, method = m, margin = 2))

    if (interactive())
      cat("took", formatC(tm[3], digits = 4), "s.\n")
    o
  }, simplify = FALSE)

  expect_true(all(sapply(os, length) == 2L))
  expect_true(all(sapply(
    os,
    FUN = function(o2)
      o2[[1]]
  ) == 1:4))
  expect_true(all(sapply(
    os,
    FUN = function(o2)
      length(o2[[2]]) == 5L
  )))

  x_p <- permute(x, os[[1]], margin = 2)
  expect_equal(x_p, x[, get_order(os[[1]], 2)])
})

test_that("test if data.frame seriation works as expected", {
  #local_edition(3) # for snapshot testing

  df <- as.data.frame(x)
  o <- seriate(df)
  expect_silent(permute(df, o)) # defaults work with no messages/warnings

  expect_warning(
    permute(df, o[1]),
    # DEPRECATED: results in a message
    "permute for data.frames with a single seriation order is now deprecated"
  )

  o <- seriate(df, margin = 1)
  expect_equal(as.integer(o[[2]]), 1:5) # columns left in original order

  oPCA <- seriate(df, method = "PCA")
  #expect_snapshot(permute(df, oPCA))
})


test_that("test if optimizes in registry is a valid criterion", {
  methods <- list_seriation_methods(names_only = FALSE)
  expect_no_error({
    for (kind in names(methods))
      for (m in methods[[kind]])
        if (!is.na(m$optimizes))
          get_criterion_method(kind, name = m$optimizes)
  })
})

