# Unit tests for indexing external logical arrays (passed from R as logical,
# represented as 0/1 integer storage inside the generated Fortran interface).

skip_on_cran()

test_that("scalar logical indexing works in ifelse()", {
  fn <- function(pred) {
    declare(type(pred = logical(3, 4)))
    out <- double(1)
    out[1] <- ifelse(pred[2, 3], 1.0, 0.0)
    out
  }

  expect_translation_snapshots(
    fn,
    note = "Regression: indexing external logical arrays must compile (no invalid subscripting of booleanized expressions)."
  )

  set.seed(1)
  pred <- matrix(sample(c(TRUE, FALSE), 12, TRUE), 3, 4)
  expect_quick_identical(fn, list(pred))
})

test_that("indexing a logical expression works (comparison)", {
  fn <- function(x) {
    declare(type(x = double(3, 4)))
    out <- double(1)
    out[1] <- ifelse(((((x > 0.0))))[2, 3], 1.0, 0.0)
    out
  }

  expect_translation_snapshots(
    fn,
    note = "Ensures indexing a logical expression compiles by hoisting the array expression to a temporary before subscripting (no invalid (expr)(i,j))."
  )

  set.seed(1)
  x <- matrix(runif(12, -1, 1), 3, 4)
  expect_quick_identical(fn, list(x))
})

test_that("indexing a logical expression works (boolean ops)", {
  fn <- function(x) {
    declare(type(x = double(3, 4)))
    out <- double(1)
    out[1] <- ifelse(((((x > 0.0) & (x < 0.5))))[2, 3], 1.0, 0.0)
    out
  }

  expect_translation_snapshots(
    fn,
    note = "Ensures indexing a boolean expression compiles by hoisting to a temporary before subscripting (no invalid (expr)(i,j))."
  )

  set.seed(1)
  x <- matrix(runif(12, -1, 1), 3, 4)
  expect_quick_identical(fn, list(x))
})

test_that("scalar logical indexing works in if() with nested boolean logic", {
  fn <- function(x, pred) {
    declare(type(x = double(3, 4)), type(pred = logical(3, 4)))
    out <- double(1)
    out[1] <- 0.0

    # exercise multiple reads of pred[i, j] inside a larger condition
    if ((pred[2, 3] && (x[2, 3] > 0.0)) || (!pred[2, 3] && (x[2, 3] < 0.0))) {
      out[1] <- x[2, 3] + 1.0
    } else {
      out[1] <- x[2, 3] - 1.0
    }

    out
  }

  set.seed(1)
  x <- matrix(runif(12, -1, 1), 3, 4)
  pred <- matrix(sample(c(TRUE, FALSE), 12, TRUE), 3, 4)
  expect_quick_identical(fn, list(x, pred))
})

test_that("slice logical indexing works in nested ifelse() expressions", {
  fn <- function(x, pred) {
    declare(type(x = double(3, 4)), type(pred = logical(3, 4)))
    out <- double(4)

    # vector mask + nested ifelse -> merge(merge(...), merge(...), mask)
    out <- ifelse(
      pred[2, ],
      ifelse(x[2, ] > 0.0, x[2, ] + 1.0, x[2, ] + 2.0),
      ifelse(x[2, ] < 0.0, x[2, ] - 1.0, x[2, ] - 2.0)
    )

    out
  }

  set.seed(1)
  x <- matrix(runif(12, -1, 1), 3, 4)
  pred <- matrix(sample(c(TRUE, FALSE), 12, TRUE), 3, 4)
  expect_quick_identical(fn, list(x, pred))
})

test_that("logical slice indexing works for pred[, j] and pred[i, ]", {
  fn <- function(pred) {
    declare(type(pred = logical(3, 4)))
    out <- double(2)
    out[1] <- sum(ifelse(pred[, 2], 1.0, 0.0))
    out[2] <- sum(ifelse(pred[3, ], 1.0, 0.0))
    out
  }

  set.seed(1)
  pred <- matrix(sample(c(TRUE, FALSE), 12, TRUE), 3, 4)
  expect_quick_identical(fn, list(pred))
})

test_that("drop = FALSE logical indexing works and compiles in larger expressions", {
  fn <- function(x, pred) {
    declare(type(x = double(3, 4)), type(pred = logical(3, 4)))
    out <- double(1)

    # keep dims (1x1), then use it in a larger scalar expression
    out[1] <- sum(ifelse(pred[2, 3, drop = FALSE], x[2, 3, drop = FALSE], 0.0))
    out[1] <- out[1] + sum(ifelse(!pred[2, 3, drop = FALSE], 1.0, 0.0))

    out
  }

  set.seed(1)
  x <- matrix(runif(12, -1, 1), 3, 4)
  pred <- matrix(sample(c(TRUE, FALSE), 12, TRUE), 3, 4)
  expect_quick_identical(fn, list(x, pred))
})

test_that("logical indexing inside nested loops drives elementwise updates", {
  fn <- function(x, pred) {
    declare(type(x = double(3, 4)), type(pred = logical(3, 4)))
    out <- matrix(0.0, 3, 4)

    for (i in 1:3) {
      for (j in 1:4) {
        if (pred[i, j]) {
          if (x[i, j] > 0.0) {
            out[i, j] <- x[i, j] * 2.0
          } else {
            out[i, j] <- (-x[i, j]) * 3.0
          }
        } else {
          # mix in scalar logical-as-double conversion to exercise both paths
          out[i, j] <- x[i, j] + as.double(pred[i, j])
        }
      }
    }

    out
  }

  set.seed(1)
  x <- matrix(runif(12, -1, 1), 3, 4)
  pred <- matrix(sample(c(TRUE, FALSE), 12, TRUE), 3, 4)
  expect_quick_identical(fn, list(x, pred))
})

test_that("rank-3 external logical indexing works in complex reductions", {
  fn <- function(x, pred) {
    declare(type(x = double(2, 3, 4)), type(pred = logical(2, 3, 4)))
    out <- double(1)
    out[1] <- 0.0

    for (i in 1:2) {
      for (j in 1:3) {
        for (k in 1:4) {
          # more complex expression mixing arithmetic and boolean logic
          if (pred[i, j, k] && (x[i, j, k] > 0.25)) {
            out[1] <- out[1] + x[i, j, k] * 2.0
          } else if ((!pred[i, j, k]) && (x[i, j, k] < -0.25)) {
            out[1] <- out[1] - x[i, j, k] * 3.0
          } else {
            out[1] <- out[1] + (x[i, j, k] + as.double(pred[i, j, k]))
          }
        }
      }
    }

    out
  }

  set.seed(1)
  x <- array(runif(24, -1, 1), dim = c(2, 3, 4))
  pred <- array(sample(c(TRUE, FALSE), 24, TRUE), dim = c(2, 3, 4))
  expect_quick_identical(fn, list(x, pred))
})

test_that("logical indexing can be combined with other vector masks and reductions", {
  fn <- function(x, pred) {
    declare(type(x = double(3, 4)), type(pred = logical(3, 4)))
    out <- double(1)

    # build a numeric row, then reduce it; mask combines pred row with x-based mask
    out[1] <- sum(ifelse(
      pred[2, ] & (x[2, ] > 0.0),
      x[2, ] * 2.0,
      x[2, ] / 2.0
    ))
    out
  }

  set.seed(1)
  x <- matrix(runif(12, -1, 1), 3, 4)
  pred <- matrix(sample(c(TRUE, FALSE), 12, TRUE), 3, 4)
  expect_quick_identical(fn, list(x, pred))
})

test_that("external logical masks work in x[pred] (pack / masked reduction)", {
  fn <- function(x, pred) {
    declare(type(x = double(3, 4)), type(pred = logical(3, 4)))
    out <- double(1)
    out[1] <- sum(x[pred])
    out
  }

  set.seed(1)
  x <- matrix(runif(12), 3, 4)
  pred <- matrix(sample(c(TRUE, FALSE), 12, TRUE), 3, 4)
  expect_quick_identical(fn, list(x, pred))
})

test_that("logical declared inputs reject integer 0/1 arrays (strict typing)", {
  fn <- function(pred) {
    declare(type(pred = logical(3, 4)))
    out <- double(1)
    out[1] <- ifelse(pred[2, 3], 1.0, 0.0)
    out
  }

  qfn <- quick(fn)
  set.seed(1)
  pred <- matrix(sample(0:1, 12, TRUE), 3, 4)
  expect_error(qfn(pred), "typeof\\(pred\\) must be 'logical'")
})

test_that("expression indexing with logical mask avoids extra materialization", {
  fn <- function(x, y, z, a) {
    declare(
      type(x = double(3, 4)),
      type(y = double(3, 4)),
      type(z = double(3, 4)),
      type(a = double(1))
    )
    out <- double(1)
    out[1] <- sum((x + y)[z > a])
    out
  }

  expect_translation_snapshots(
    fn,
    note = "Mask subsetting should stay as a single pack-like operation (avoid pushing mask into operands and duplicating mask evaluation)."
  )

  set.seed(1)
  x <- matrix(runif(12), 3, 4)
  y <- matrix(runif(12), 3, 4)
  z <- matrix(runif(12), 3, 4)
  a <- 0.5
  expect_quick_identical(fn, list(x, y, z, a))
})
