# Unit tests for matrix creation and implicit size reuse

skip_on_cran()

test_that("matrix", {
  fn <- function(a, b) {
    declare(type(a = integer(1)))
    declare(type(b = integer(1)))

    out <- matrix(0, a, b)
    out
  }

  fsub <- r2f(fn)
  make_c_bridge(fsub)

  (r2f(fn))
  expect_snapshot(r2f(fn))

  qfn <- quick(fn)

  # expect_identical(qfn(3, 4), fn(3, 4))  ## strict = TRUE by default
  # expect_identical(qfn(3L, 4), fn(3L, 4))
  expect_identical(qfn(3L, 4L), fn(3L, 4L))

  fn <- function(val, nc, nr) {
    # declare({
    #   type(val = double(1))
    #   type(a = integer(1))
    #   type(b = integer(1))
    # })
    declare(
      type(val = double(1)),
      type(nc = integer(1)),
      type(nr = integer(1))
    )

    out <- matrix(val, nc, nr)
    out
  }
  qfn <- quick(fn)

  qfn(1.1, 3L, 3L)

  expect_identical(qfn(2.3, 3L, 4L), fn(2.3, 3L, 4L))
  expect_identical(qfn(2.3, 3L, 4L), matrix(2.3, 3L, 4L))
  # bench::mark(fn(2.3, 3, 4), matrix(2.3, 3, 4), qfn(2.3, 3, 4)) -> r; print(r); plot(r)
})

test_that("matrix() reshapes non-scalar data expressions", {
  fn <- function(x) {
    declare(type(x = double(2L, 3L)))
    matrix(as.integer(x), nrow = 2L, ncol = 3L)
  }

  x <- matrix(as.double(1:6), nrow = 2L, ncol = 3L)
  expect_quick_identical(fn, list(x))
})

test_that("matrix() does not evaluate data expressions twice", {
  fn <- function() {
    matrix(runif(4L), nrow = 2L, ncol = 2L)
  }
  qfn <- quick(fn)

  set.seed(1234)
  qfn()
  q_next <- runif(1L)

  set.seed(1234)
  fn()
  r_next <- runif(1L)

  expect_equal(q_next, r_next)
})

test_that("reuse implicit size", {
  fn <- function(a1, a2) {
    declare(type(a1 = double(n)))
    declare(type(a2 = double(n, n)))
    out <- a1 + a2[1, ]
    out
  }

  fsub <- r2f(fn)
  c_wrapper <- make_c_bridge(fsub)
  qfn <- quick(fn)

  expect_snapshot({
    print(fsub)
    cat(c_wrapper)
  })

  n <- 400
  a1 <- as.double(1:n)
  a2 <- matrix(runif(n), n, n)

  expect_identical(fn(a1, a2), qfn(a1, a2))

  # bench::mark(fn(a1, a2), qfn(a1, a2)) |> print() |> plot()
})

test_that("vector-matrix ops only recycle along the first dimension", {
  fn <- function(vec, mat) {
    declare(
      type(vec = double(3)),
      type(mat = double(1L, 3))
    )
    left_side_vec <- vec + mat
    right_side_vec <- mat + vec
    out <- left_side_vec + right_side_vec
    out
  }

  expect_error(
    quick(fn),
    "elementwise vector-matrix operations require a scalar or a vector length equal to the matrix first dimension (nrow)",
    fixed = TRUE
  )
})

test_that("elementwise vector and singleton column matrix keep matrix shape", {
  fn <- function(vec, mat) {
    declare(
      type(vec = double(n)),
      type(mat = double(n, 1L))
    )
    left_side_vec <- vec + mat
    right_side_vec <- mat + vec
    out <- left_side_vec + right_side_vec
    out
  }

  expect_quick_identical(
    fn,
    list(runif(4), matrix(runif(4), ncol = 1L))
  )
})

test_that("elementwise vector-matrix reuse works across columns", {
  fn <- function(vec, mat) {
    declare(
      type(vec = double(3)),
      type(mat = double(3, 2))
    )
    left <- vec * mat
    right <- mat * vec
    left + right
  }

  vec <- as.double(1:3)
  mat <- matrix(as.double(1:6), nrow = 3)

  expect_quick_identical(fn, list(vec, mat))
})

test_that("elementwise vector-matrix ops allow scalar vectors", {
  fn <- function(vec, mat) {
    declare(
      type(vec = double(1)),
      type(mat = double(3, 2))
    )
    vec + mat
  }

  vec <- 2.5
  mat <- matrix(as.double(1:6), nrow = 3)

  expect_quick_identical(fn, list(vec, mat))
})

test_that("elementwise vector-matrix ops reject longer vectors", {
  fn <- function(vec, mat) {
    declare(
      type(vec = double(2)),
      type(mat = double(3, 2))
    )
    vec + mat
  }

  expect_error(
    quick(fn),
    "elementwise vector-matrix operations require a scalar or a vector length equal to the matrix first dimension (nrow)",
    fixed = TRUE
  )
})

test_that("elementwise matrix operations require matching dimensions", {
  fn <- function(a, b) {
    declare(
      type(a = double(2, 3)),
      type(b = double(2, 2))
    )
    a + b
  }

  expect_error(
    quick(fn),
    "elementwise matrix operations require matching dimensions",
    fixed = TRUE
  )
})

test_that("elementwise vector operations require matching lengths", {
  fn <- function(a, b) {
    declare(
      type(a = double(2)),
      type(b = double(3))
    )
    a + b
  }

  expect_error(
    quick(fn),
    "elementwise vector operations require lengths that recycle cleanly unless one operand is scalar",
    fixed = TRUE
  )
})

test_that("elementwise vector ops allow scalar vectors", {
  fn <- function(a, b) {
    declare(
      type(a = double(1)),
      type(b = double(3))
    )
    a + b
  }

  expect_quick_identical(fn, list(2.5, as.double(1:3)))
})

test_that("elementwise ops reshape vectors for singleton matrices", {
  fn <- function(vec, mat) {
    declare(
      type(vec = double(n)),
      type(mat = double(n, 1L))
    )
    a <- vec - mat
    b <- mat * vec
    c <- vec / mat
    a + b + c
  }

  expect_quick_identical(
    fn,
    list(runif(3) + 1, matrix(runif(3) + 1, ncol = 1L))
  )
})

test_that("1x1 matrix preserves matrix result with length-1 vector", {
  fn <- function(vec, mat) {
    declare(
      type(vec = double(1L)),
      type(mat = double(1L, 1L))
    )
    vec + mat
  }

  expect_quick_identical(
    fn,
    list(runif(1), matrix(runif(1), nrow = 1L))
  )
})

test_that("1x1 matrix with length-3 vector yields a vector", {
  fn <- function(vec, mat_1_1) {
    declare(
      type(vec = double(3)),
      type(mat_1_1 = double(1, 1))
    )
    a <- vec + mat_1_1
    b <- mat_1_1 + vec
    out <- list(a = a, b = b)
    out
  }

  suppressWarnings(expect_quick_identical(
    fn,
    list(c(1, 2, 3), matrix(1, nrow = 1L, ncol = 1L))
  ))
})

test_that("indexing function like transposed expressions hoists temporaries that can be accessed", {
  fn <- function(x) {
    declare(type(x = double(5, 5)))
    first_element <- t(x)[1]
    second_row <- t(x)[2, ]
    third_col <- t(x)[, 3]
    sub_matrix <- t(x)[c(1, 2), c(3, 4)]
    list(
      first_element = first_element,
      second_row = second_row,
      third_col = third_col,
      sub_matrix = sub_matrix
    )
  }

  x <- matrix(runif(25), 5, 5)
  expect_quick_identical(fn, list(x = x))
})
