# ------------------------------------------------------------------------------
# Setup

ex_eq <- c(0L, 1L, 2L, 2L, 3L)
ex_no_eq <- c(1L, 1L, 2L, 2L, 3L)
real_labs <- c("low", "med", "high")

factor_no_eq <- factor(ex_no_eq, labels = real_labs)

manual_creation_eq <- new_class_pred(ex_eq, labels = real_labs)
manual_creation_no_eq <- new_class_pred(ex_no_eq, labels = real_labs)

masked_high <- class_pred(factor_no_eq, which = 5)

# ------------------------------------------------------------------------------
# Testing

test_that("can create from integers", {
  expect_equal(new_class_pred(ex_eq, real_labs), manual_creation_eq)
  expect_equal(new_class_pred(ex_no_eq, real_labs), manual_creation_no_eq)

  expect_error(new_class_pred("not-an-integer", "lab"))
  expect_error(new_class_pred(1L, 0))
})

test_that("can create from helper", {
  expect_equal(class_pred(factor_no_eq, which = 1), manual_creation_eq)
  expect_equal(class_pred(factor_no_eq), manual_creation_no_eq)

  expect_error(class_pred(1L))
})

test_that("can detect class_pred", {
  expect_equal(is_class_pred(manual_creation_eq), TRUE)
})

test_that("equivocal label is not allowed as level", {
  expect_error(class_pred(factor("[EQ]")))

  # changing the label means we can use "EQ"
  expect_error(class_pred(factor("[EQ]"), equivocal = "hi"), NA)
})

test_that("format preserves equivocal values", {
  expect_equal(
    format(manual_creation_eq),
    c("[EQ]", "low", "med", "med", "high")
  )
})

test_that("levels() does not return the equivocal label", {
  expect_equal(levels(manual_creation_eq), c("low", "med", "high"))
})

test_that("class_pred masking a level retains the level", {
  expect_equal(
    levels(masked_high),
    c("low", "med", "high")
  )
})

test_that("coercing class_pred to factor retains all levels", {
  expect_equal(
    levels(masked_high),
    levels(as.factor(masked_high))
  )
})

test_that("class_pred can be coerced to ordered factor", {
  expect_s3_class(as.ordered(manual_creation_eq), "ordered")
})

test_that("casting class_pred to class_pred", {
  cp1 <- class_pred(factor(c("a", "b", "b", "c")), which = 2)
  cp2 <- class_pred(factor(c("a", "b", "b", "b")), which = 3)
  cp3 <- class_pred(factor(c("a", "b", "b", "c")), which = 2, equivocal = "eq")
  cp4 <- class_pred(factor(c("a", "b", "b", "c"), ordered = TRUE), which = 2)

  # lossy cast, no c level in cp2
  expect_error(vec_cast(cp1, cp2), class = "vctrs_error_cast_lossy")

  # can suppress lossy cast
  expect_equal(
    allow_lossy_cast(vec_cast(cp1, cp2)),
    class_pred(factor(c("a", "b", "b", NA)), which = 2L)
  )

  # casting to new class_pred preserves new eq label
  expect_equal(get_equivocal_label(vec_cast(cp1, cp3)), "eq")

  expect_true(is_ordered(vec_cast(cp1, cp4)))
})

test_that("casting class_pred to factor", {
  cp1 <- class_pred(factor(c("a", "b", "b", "c")))
  cp2 <- class_pred(factor(c("a", "b", "b", "c")), which = 2)
  cp3 <- class_pred(factor(c(NA, "a", "b", "c")), which = 3)
  fc1 <- factor(levels = c("a", "b"))
  fc2 <- factor(levels = c("a", "b", "c"))
  fc3 <- factor(levels = c("a", "b", "c"), ordered = TRUE)

  # lossy cast, no c level in fc1
  expect_error(vec_cast(cp1, fc1), class = "vctrs_error_cast_lossy")

  # can allow lossy cast to succeed
  expect_equal(
    allow_lossy_cast(vec_cast(cp1, fc1)),
    factor(c("a", "b", "b", NA))
  )

  # clean conversion to factor
  expect_equal(vec_cast(cp1, fc2), factor(c("a", "b", "b", "c")))

  # convert to factor, eq becomes NA
  expect_equal(vec_cast(cp2, fc2), factor(c("a", NA, "b", "c")))

  # converting to ordered factor maintains orderedness
  expect_equal(vec_cast(cp1, fc3), ordered(c("a", "b", "b", "c")))

  # convert to factor with NA already present is not lossy
  expect_warning(vec_cast(cp3, factor()), NA)

  # special test for when the factor you are casting to has different
  # levels and they are in an odd order compared to what the class_pred had
  # (poor is between good and great here)
  cp_special <- class_pred(factor(c("good", "great")))
  fc_special <- factor(c("good", "poor", "great"), c("good", "poor", "great"))
  res_special <- factor(c("good", "great"), levels = c("good", "poor", "great"))
  expect_equal(vec_cast(cp_special, fc_special), res_special)
})

test_that("casting factor to class_pred", {
  fc1 <- factor(c("a", "b", "b", "b"))
  fc2 <- factor(c("a", "b", "b", "c"))
  fc3 <- factor(c(NA, "a", "b", "c"))

  cp1 <- class_pred(factor(levels = c("a", "b")))
  cp2 <- class_pred(factor(levels = c("a", "b", "c")))
  cp3 <- class_pred(factor(levels = c("a", "b", "c")), equivocal = "eq")
  cp4 <- class_pred(factor(levels = c("a", "b", "c"), ordered = TRUE))

  # lossy cast, no c level in cp1
  expect_error(vec_cast(fc2, cp1), class = "vctrs_error_cast_lossy")

  # can allow lossy cast to succeed
  expect_equal(
    allow_lossy_cast(vec_cast(fc2, cp1)),
    class_pred(factor(c("a", "b", "b", NA)))
  )

  # clean conversion to class_pred
  expect_equal(vec_cast(fc1, cp1), class_pred(factor(c("a", "b", "b", "b"))))

  # converting to ordered class_pred maintains orderedness
  expect_equal(vec_cast(fc2, cp4), class_pred(factor(c("a", "b", "b", "c"), ordered = TRUE)))

  # convert to class_pred with NA already present is not lossy
  expect_warning(vec_cast(fc3, class_pred()), NA)

  # convert ordered factor to class_pred
  # order-ness depends on class_pred type, not order factor
  or1 <- as.ordered(fc1)
  expect_equal(vec_cast(or1, class_pred()), class_pred(factor(c("a", "b", "b", "b"))))
})

test_that("casting character to class_pred", {
  chr1 <- c("a", "b", "b", "c")
  cp1 <- class_pred(factor(c("a", "b", "b", "c")))
  cp2 <- class_pred(factor(c("a", "b", "b", "b")))
  cp3 <- class_pred(factor(c("a", "b", "b", "c"), ordered = TRUE))
  cp4 <- class_pred(factor(c("a", "b", "b", "c")), equivocal = "eq")

  # lossy cast, no c level in chr1
  expect_error(vec_cast(chr1, cp2), class = "vctrs_error_cast_lossy")

  # can allow lossy cast to succeed
  expect_equal(
    allow_lossy_cast(vec_cast(chr1, cp2)),
    class_pred(factor(c("a", "b", "b", NA)))
  )

  # equivocal label is maintained
  expect_equal(get_equivocal_label(vec_cast(chr1, cp4)), "eq")

  # clean conversion to class_pred
  expect_equal(vec_cast(chr1, cp1), class_pred(factor(c("a", "b", "b", "c"))))

  # converting to ordered class_pred maintains orderedness
  expect_true(is_ordered_class_pred(vec_cast(chr1, cp3)))
})

test_that("slicing", {
  # levels are kept
  expect_equal(levels(manual_creation_eq[1]), real_labs)

  # reportable rate updates
  expect_equal(reportable_rate(manual_creation_eq[1]), 0)
  expect_equal(reportable_rate(manual_creation_eq[1:2]), 0.5)

  # extending past is an error
  expect_snapshot(error = TRUE, {
    manual_creation_eq[1:6]
  })
})

test_that("unknown casts are handled correctly", {
  # numeric -> class_pred = error
  expect_error(vec_cast(numeric(), class_pred()))

  # logical vec -> class pred = depends on if only NA or has TRUE/FALSE
  expect_equal(vec_cast(NA, class_pred()), class_pred(factor(NA)))
  expect_error(vec_cast(TRUE, class_pred()))

  # NULL second = x, NULL first = NULL
  expect_equal(vec_cast(NULL, class_pred()), NULL)
  expect_equal(vec_cast(class_pred(), NULL), class_pred())
})

test_that("ptype2 checks are handled correctly", {
  expect_error(vec_ptype2(manual_creation_eq, numeric()), class = "vctrs_error_incompatible_type")
  expect_equal(vec_ptype2(manual_creation_eq, vctrs::unspecified()), vec_ptype(manual_creation_eq))

  expect_equal(vec_ptype2(character(), manual_creation_eq), character())
  expect_equal(vec_ptype2(manual_creation_eq, character()), character())
})

test_that("combining class preds", {
  cp1 <- new_class_pred(c(1L, 2L, 2L), c("low", "med"))
  cp2 <- new_class_pred(c(1L, 2L, 2L), c("low", "med"), ordered = TRUE)
  cp3 <- new_class_pred(c(1L, 2L, 2L), c("low", "med"), equivocal = "eq")

  # joining with different levels is the union
  expect_equal(levels(c(manual_creation_eq, cp1)), c("low", "med", "high"))

  # joining with different levels is the union in the order given
  # even if one is ordered
  expect_equal(levels(c(manual_creation_eq, cp2)), c("low", "med", "high"))
  expect_true(is_ordered_class_pred(c(manual_creation_eq, cp2)))

  # joining with different equivocal labels uses the LHS label
  expect_equal(get_equivocal_label(c(cp1, cp3)), "[EQ]")
  expect_equal(get_equivocal_label(c(cp3, cp1)), "eq")
})

test_that("combining class pred with factor", {
  cp1 <- class_pred(factor(c("good", "poor")), which = 2)
  chr <- c("good", "great", NA)
  fc1 <- factor(chr)

  join1 <- class_pred(
    factor(
      c("good", "poor", "good", "great", NA),
      levels = c("good", "poor", "great")
    ),
    which = 2
  )

  join2 <- class_pred(
    factor(
      c("good", "great", NA, "good", "poor"),
      levels = c("good", "great", "poor")
    ),
    which = 5
  )

  join3 <- c(1, 2, NA, 1, 0)

  # vec_c() joins are bidirectionally correct
  expect_equal(vec_c(cp1, fc1), join1)
  expect_equal(vec_c(fc1, cp1), join2)

  # c() joins are correct if vctrs_vctr is first
  expect_equal(c(cp1, fc1), join1)

  # sadly this happens and cannot be overriden
  expect_equal(c(fc1, cp1), join3)
})

test_that("common type: factor and class_pred", {
  expect_s3_class(vec_ptype2(class_pred(), factor()), "class_pred")

  expect_equal(
    levels(
      vec_ptype2(
        class_pred(factor(levels = "a")),
        factor(levels = "b")
      )
    ),
    c("a", "b")
  )

  expect_equal(
    get_equivocal_label(
      vec_ptype2(
        class_pred(factor(levels = "a"), equivocal = "eq"),
        factor(levels = "b")
      )
    ),
    "eq"
  )

  # reverse order

  expect_s3_class(vec_ptype2(factor(), class_pred()), "class_pred")

  expect_equal(
    levels(
      vec_ptype2(
        factor(levels = "b"),
        class_pred(factor(levels = "a"))
      )
    ),
    c("b", "a")
  )

  expect_equal(
    get_equivocal_label(
      vec_ptype2(
        factor(levels = "b"),
        class_pred(factor(levels = "a"), equivocal = "eq")
      )
    ),
    "eq"
  )
})

test_that("smaller vctrs helpers", {
  expect_equal(vec_ptype_abbr(manual_creation_eq), "clss_prd")
})

test_that("reportable rate", {
  report_100 <- class_pred(factor(1))
  report_0 <- class_pred()
  report_50 <- class_pred(factor(c(1, 2)), 2)
  report_667 <- class_pred(factor(c(1, 2, 3)), 2)

  expect_output(cat_reportable(report_100), "Reportable: 100%")
  expect_output(cat_reportable(report_0), "Reportable: 0%")
  expect_output(cat_reportable(report_50), "Reportable: 50%")
  expect_output(cat_reportable(report_667), "Reportable: 66.7%")

  expect_error(reportable_rate(1), "No implementation")
})

test_that("constructor aborts on bad input", {
  expect_error(class_pred(which = "hello"))
  expect_error(class_pred(equivocal = 1))
  expect_error(class_pred(which = 1), "The largest value of `which` can be 0.")
})

test_that("as_class_pred() works like the constructor", {
  expect_equal(class_pred(factor_no_eq, 1), as_class_pred(factor_no_eq, 1))
  expect_error(as_class_pred(which = "hello"))
  expect_error(as_class_pred(1), "No implementation")
})

test_that("locate equivocal helpers", {
  expect_equal(is_equivocal(manual_creation_eq), c(TRUE, rep(FALSE, 4)))
  expect_error(is_equivocal("a"), "No implementation")

  expect_equal(which_equivocal(manual_creation_eq), 1L)
  expect_error(which_equivocal("a"), "No implementation")

  expect_equal(any_equivocal(manual_creation_eq), TRUE)
  expect_error(any_equivocal("a"), "No implementation")
})

test_that("class_pred printing", {
  expect_output(
    cat_class_pred(manual_creation_eq),
    "\\[1\\] \\[EQ\\] low  med  med  high"
  )

  expect_output(
    cat_class_pred(class_pred()),
    "class_pred\\(0\\)"
  )

  expect_output(cat_levels(class_pred()), "Levels:")
  expect_output(cat_levels(manual_creation_eq), "Levels: low med high")

  expect_output(
    cat_levels(class_pred(as.ordered(factor_no_eq))),
    "Levels: low < med < high"
  )

  # long levels
  fctrs <- paste0("blaaaaaaaaaaaaaaaaaaaaaaaaa", c(1, 2, 3, 4))
  cp <- class_pred(as.factor(fctrs))

  expect_output(
    cat_levels(cp),
    "4 Levels: blaaaaaaaaaaaaaaaaaaaaaaaaa1 ... blaaaaaaaaaaaaaaaaaaaaaaaaa4"
  )
})
