context("glomming clods - ragged horizon intersection for SPCs")

data(sp1, package = 'aqp')
depths(sp1) <- id ~ top + bottom
site(sp1) <- ~ group

p <- sp1[6]
attr <- 'prop' # clay contents %

test_that("intersection of horizons by depth", {
    # intersection at a single depth should return only one horizon
    expect_equal(sum(glom(p, 50, ids = TRUE) %in% hzID(p)), 1)

    # intersection from 25 to 100 should return four horizons
    expect_equal(sum(glom(p, 25, 100, ids = TRUE) %in% hzID(p)), 4)
})

test_that("depthWeights parity", {
  # depthWeights calculates contributing fractions within glom intervals for a profile p
  wts <- depthWeights(p$top, p$bottom, 25, 100)

  # wrong length errors
  expect_error(depthWeights(p$top, p$bottom, c(25,50), 100))
  expect_error(depthWeights(p$top[1], p$bottom, 25, 100))

  expect_equal(wts, c(0, 0, 0.32, 0.186666666666667, 0.36, 0.133333333333333, 0, 0, 0, 0))
  expect_equal(sum(wts), 1)
  glmp <- glom(p, 25, 100, truncate = TRUE)
  glmp$thk <- glmp$bottom - glmp$top
  glmp$relthk <- glmp$thk / sum(glmp$thk)
  expect_equal(glmp$relthk, wts[wts > 0])
})

test_that("degenerate single horizon case", {
    # glom tests with a degenerate case
    test <- data.frame(id = 1, top = 0, bottom = 100)
    depths(test) <- id ~ top + bottom

    # default glom
    expect_silent(t1 <- glom(test, 25, 50))
    expect_equal(t1$bottom - t1$top, 100)

    # thickest-modality default glom
    expect_silent(t1 <- glom(test, 25, 50, modality = "thickest"))
    expect_equal(t1$bottom - t1$top, 100)

    # truncate glom
    expect_silent(t1 <- glom(test, 25, 50, truncate = TRUE))
    expect_equal(t1$bottom - t1$top, 25)

    # thickest-modality truncate glom
    expect_silent(t1 <- glom(test, 25, 50, truncate = TRUE, modality = "thickest"))
    expect_equal(t1$bottom - t1$top, 25)

    # invert glom
    expect_silent(t1 <- glom(test, 25, 50, invert = TRUE))
    expect_equal(t1$bottom - t1$top, 100)

    # invert + truncate glom
    expect_silent(t1 <- glom(test, 25, 50, invert = TRUE, truncate = TRUE))
    expect_equal(t1$bottom - t1$top, c(25, 50))

    # thickest-modality invert + truncate glom
    expect_silent(t1 <- glom(test, 25, 50, invert = TRUE, truncate = TRUE, modality = "thickest"))
    expect_equal(t1$bottom - t1$top, 50)
})

test_that("glom by depth returns a SPC clod", {
  # glom 'gloms' your input SPC `p`'s horizons (by depths specified) into a 'clod'

  # currently "clods" can be either represented as an SPC, or a data.frame with just
  # the horizons that are contained within the "clod".
  foo <- glom(p, 25, 100)
  # and returns an SPC
  expect_true(inherits(foo, 'SoilProfileCollection'))

  # within that SPC there should be only one profile
  expect_equal(length(foo), 1)

  # and that profile should have 4 horizons in 25-100cm
  expect_equal(nrow(foo), 4)
})

test_that("glom by depth returns a data.frame clod", {
  # glom 'gloms' your input SPC `p`'s horizons (by depths specified) into a 'clod'

  # currently "clods" can be either represented as an SPC, or a data.frame with just
  # the horizons that are contained within the "clod".
  foo <- glom(p, 25, 100, df = TRUE)

  # and returns an data.frame
  expect_true(inherits(foo, 'data.frame'))

  # within that data.frame, length() returns 18
  expect_equal(length(foo), 18)

  # and that data.frame should have 4 horizons (rows) in 25-100cm
  expect_equal(nrow(foo), 4)
})

test_that("glom truncate = TRUE works as expected", {
  # glom 'gloms' your input SPC `p`'s horizons (by depths specified) into a 'clod'

  # get truncated clod
  foo <- glom(p, 25, 100, truncate=TRUE)

  # and returns an data.frame
  expect_true(inherits(foo, 'SoilProfileCollection'))

  ## test that:
  # shallowest top truncated to z1
  # deepest bottom truncated to z2
  expect_equal(min(foo$top), 25)
  expect_equal(max(foo$bottom), 100)

})

test_that("glomApply works as expected", {

  # using same depth returns a single horizon from every profile
  # glomApply with same z1 as z2
  expect_silent(res <- glomApply(sp1, function(p) return(c(25,25))))

  # above is the same as calling glom with just z1 specified (for first profile in sp1)
  expect_equivalent(res[1,], glom(sp1[1,], 25))

  # after pbindlist hzID 3 becomes 1 (since sp1 does not have a true hzID specified)
  tdepths <- horizonDepths(sp1)
  expect_equal(horizons(res[1,])[,tdepths], horizons(glom(sp1[1,], 25))[,tdepths])

  # every profile returns one horizon (all profiles at least 25cm deep)
  expect_true(all(profileApply(res, nrow) == 1))

  # 7 profiles have 200 as an invalid upper bound
  ## note that P006 and P008 end EXACTLY at 200 and are NOT included
  # plot(sp1)
  # abline(h = 200)
  expect_silent(res2 <- glomApply(sp1, function(p) return(c(200,200))))

  expect_equal(names(profileApply(res2, nrow)), c("P007", "P009"))

  test_that("realistic glomApply scenarios", {
    data(sp3)
    depths(sp3) <- id ~ top + bottom
    hzdesgnname(sp3) <- 'name'

    # constant depths, whole horizon returns by default
    expect_silent(glomApply(sp3, function(p) c(25,100)))

    # constant depths, truncated
    #(see aqp::trunc for helper function)
    expect_silent(glomApply(sp3, function(p) c(25,30), truncate = TRUE))

    # constant depths, inverted
    expect_silent(glomApply(sp3, function(p) c(25,100), invert = TRUE))

    # constant depths, inverted + truncated (same as above)
    expect_silent(res <- glomApply(sp3, function(p) c(25,30), invert = TRUE, truncate = TRUE))

    # before invert, 46 horizons
    expect_equal(nrow(sp3), 46)

    # after glom invert, some horizons are split, increasing the total number
    expect_equal(nrow(res), 52)

    # random boundaries in each profile the specific warnings are a product of pseudorandom numbers
    set.seed(100)
    expect_silent(glomApply(sp3, function(p) round(sort(runif(2, 0, max(sp3))))))

    # random boundaries in each profile (truncated)
    expect_silent(glomApply(sp3, function(p) round(sort(runif(2, 0, max(sp3)))), truncate = TRUE))

    # calculate some boundaries as site level attribtes
    expect_warning(sp3$glom_top <- profileApply(sp3, getMineralSoilSurfaceDepth))
    expect_silent(sp3$glom_bottom <- profileApply(sp3, estimateSoilDepth))

    # use site level attributes for glom intervals for each profile
    expect_silent(glomApply(sp3, function(p) return(c(p$glom_top, p$glom_bottom))))
  })

  # trunc using vectorized glom v.s. iterating with constant depths works as expected
  expect_equivalent({
      glomApply(sp1, function(p) return(c(25,36)), truncate = TRUE)
    },{
      trunc(sp1, 25, 36)
    })
})

test_that("glom vectorization", {
  # 4 of 9 profiles dropped
  expect_equal(length(glom(sp1, 75, 100)), 5)

  # truncated max SPC depth is 100cm
  expect_equal(max(glom(sp1, 75, 100, truncate = TRUE)), 100)

  # truncated and inverted
  t3 <- glom(sp1, 75, 100, truncate = TRUE, invert = TRUE)

  # all of profiles in invert result
  expect_equal(length(t3), 9)

  # max SPC depth is 240cm
  expect_equal(max(t3), 240)
  
  z1 <- c(rep(c(0,55), 5))[1:9]
  z2 <- c(rep(c(10,60), 5))[1:9]
  
  # profile specific glom boundaries (alternating [0,10] [55,60])
  t4 <- glom(sp1, z1, z2)
  expect_equal(t4$bottom, c(2L, 14L, 59L, 2L, 13L, 62L, 5L,
                            30L, 63L, 4L, 16L, 68L, 3L, 14L))  
 
  # truncate = TRUE using same boundaries 
  t5 <- glom(sp1, z1, z2, truncate = TRUE)
  expect_equal(t5$bottom, c(2L, 10L, 59L, 2L, 10L, 60L, 5L, 
                            10L, 60L, 4L, 10L, 60L, 3L, 10L))  
  
  # truncate twice, initially with drop=FALSE
  expect_equal(length(trunc(trunc(sp1, 75, 100, drop = FALSE), 90, 100)), 4)
  
  # no horizons in glom interval, with missing profiles dropped
  expect_equal(length(trunc(sp1, 250, 300)), 0)
  
  # no horizons in glom interval, with missing profiles filled
  expect_equal(length(trunc(sp1, 250, 300, drop = FALSE)), 9)
})

