# Tests for the pressuRe package
# Install:  install.packages("pressuRe")
#           remotes::install_github("Telfer/pressuRe")
# Run with: testthat::test_file("test-pressuRe.R")
#
# Current pressuRe data structure (list with named fields):
#   $pressure_array   – 2D numeric matrix, rows = timepoints, cols = sensors
#   $pressure_system  – character string identifying the hardware system
#   $sens_size        – character string identifying the sensor type/size
#   $time             – numeric, time interval between measurements (seconds)
#   $masks            – list of masks
#   $events           – list of event markers
#   $sensor_polygons  – data frame with sensor corner coordinates
#   $max_matrix       – matrix of per-sensor maximum values
#
# NOTE: if any function name or argument has drifted from what is tested here,
# check ls("package:pressuRe") and update accordingly.

library(testthat)
library(pressuRe)

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

# Number of fake sensors and timepoints used across tests
N_SENSORS   <- 48L   # columns of pressure_array
N_TIMEPOINTS <- 100L  # rows of pressure_array

# Build a minimal valid pressuRe list with uniform pressure
make_pressure_obj <- function(n_timepoints = N_TIMEPOINTS,
                              n_sensors    = N_SENSORS,
                              value        = 200,
                              time         = 0.01,   # 100 Hz
                              pressure_system = "emed",
                              sens_size    = 0.005 * 0.005) {

  pressure_array <- matrix(value,
                           nrow = n_timepoints,
                           ncol = n_sensors)

  # Minimal sensor_polygons: one row per sensor with an x/y centroid
  sensor_polygons <- data.frame(
    sensor_id = seq_len(n_sensors),
    x         = rep(seq_len(n_sensors / 8), each = 8),
    y         = rep(seq_len(8), times = n_sensors / 8)
  )

  # max_matrix: per-sensor max (for uniform data, equals value)
  max_matrix <- matrix(value, nrow = 1L, ncol = n_sensors)

  list(
    pressure_array  = pressure_array,
    pressure_system = pressure_system,
    sens_size       = sens_size,
    time            = time,
    masks           = list(),
    events          = list(),
    sensor_polygons = sensor_polygons,
    max_matrix      = max_matrix
  )
}

# Ramp: pressure rises from 0 to peak and back to 0 (sinusoidal loading curve)
make_ramp_obj <- function(n_timepoints = N_TIMEPOINTS,
                          n_sensors    = N_SENSORS,
                          peak         = 300,
                          time         = 0.01) {
  ramp <- outer(sin(pi * seq_len(n_timepoints) / n_timepoints),
                rep(peak, n_sensors))
  obj <- make_pressure_obj(n_timepoints, n_sensors, value = 0, time = time)
  obj$pressure_array <- ramp
  obj$max_matrix     <- matrix(apply(ramp, 2, max), nrow = 1L)
  obj
}

make_zero_obj <- function(...) make_pressure_obj(..., value = 0)

# ---------------------------------------------------------------------------
# 1.  Package loads and key symbols are exported
# ---------------------------------------------------------------------------

test_that("pressuRe package loads without errors", {
  expect_true("package:pressuRe" %in% search())
})

test_that("core functions are exported", {
  exported <- ls("package:pressuRe")
  expected <- c(
    "load_emed", "load_pedar",
    "pressure_interp", "auto_detect_side",
    "cop", "cpei",
    "whole_pressure_curve", "mask_analysis",
    "create_mask_auto", "create_mask_manual"
  )
  for (fn in expected) {
    expect_true(fn %in% exported,
                info = paste("Expected export missing:", fn))
  }
})


# ---------------------------------------------------------------------------
# 2A.  Data structure integrity helpers
# ---------------------------------------------------------------------------

test_that("make_pressure_obj produces the expected list structure", {
  obj <- make_pressure_obj()
  expect_true(is.list(obj))
  for (field in c("pressure_array", "pressure_system", "sens_size",
                  "time", "masks", "events", "sensor_polygons", "max_matrix")) {
    expect_true(field %in% names(obj), info = paste("Field missing:", field))
  }
})

test_that("pressure_array is a 2D matrix with correct dimensions", {
  obj <- make_pressure_obj(n_timepoints = 80, n_sensors = 32)
  expect_true(is.matrix(obj$pressure_array))
  expect_equal(nrow(obj$pressure_array), 80)
  expect_equal(ncol(obj$pressure_array), 32)
})

test_that("max_matrix columns match column-wise maxima of pressure_array", {
  obj <- make_ramp_obj()
  expected_max <- apply(obj$pressure_array, 2, max)
  expect_equal(as.numeric(obj$max_matrix), expected_max, tolerance = 1e-6)
})

# ---------------------------------------------------------------------------
# 2B.  Data structure integrity helpers (EMED)
# ---------------------------------------------------------------------------

test_that("make_pressure_obj produces the expected list structure", {
  emed_data <- system.file("extdata", "emed_test.lst", package = "pressuRe")
  obj <- load_emed(emed_data)
  expect_true(is.list(obj))
  for (field in c("pressure_array", "pressure_system", "sens_size",
                  "time", "masks", "events", "sensor_polygons", "max_matrix")) {
    expect_true(field %in% names(obj), info = paste("Field missing:", field))
  }
})

test_that("pressure_array is a 2D matrix with correct dimensions", {
  emed_data <- system.file("extdata", "emed_test.lst", package = "pressuRe")
  obj <- load_emed(emed_data)
  expect_true(is.matrix(obj$pressure_array))
  expect_equal(nrow(obj$pressure_array), 106)
  expect_equal(ncol(obj$pressure_array), 506)
})

test_that("each sensor had an associated polygon", {
  emed_data <- system.file("extdata", "emed_test.lst", package = "pressuRe")
  obj <- load_emed(emed_data)
  sens_poly <- obj[[7]]
  n_poly <- length(unique(sens_poly$id))
  expect_equal(ncol(obj$pressure_array), n_poly)
})

# ---------------------------------------------------------------------------
# 2C.  Data structure integrity helpers (pedar)
# ---------------------------------------------------------------------------

test_that("make_pressure_obj produces the expected list structure", {
  pedar_data <- system.file("extdata", "pedar_example.asc", package = "pressuRe")
  obj <- load_pedar(pedar_data)
  expect_true(is.list(obj))
  for (field in c("pressure_array", "pressure_system", "sens_size",
                  "time", "masks", "events", "sensor_polygons", "max_matrix")) {
    expect_true(field %in% names(obj), info = paste("Field missing:", field))
  }
})

test_that("pressure_array is a 2D matrix with correct dimensions", {
  pedar_data <- system.file("extdata", "pedar_example.asc", package = "pressuRe")
  obj <- load_pedar(pedar_data)
  expect_true(is.matrix(obj$pressure_array))
  expect_equal(nrow(obj$pressure_array), 1505)
  expect_equal(ncol(obj$pressure_array), 198)
})

# ---------------------------------------------------------------------------
# 2C.  Data structure integrity helpers (pliance)
# ---------------------------------------------------------------------------

test_that("make_pressure_obj produces the expected list structure", {
  pliance_data <- system.file("extdata", "pliance_test.asc", package = "pressuRe")
  obj <- load_pliance(pliance_data)
  expect_true(is.list(obj))
  for (field in c("pressure_array", "pressure_system", "sens_size",
                  "time", "masks", "events", "sensor_polygons", "max_matrix")) {
    expect_true(field %in% names(obj), info = paste("Field missing:", field))
  }
})

test_that("pressure_array is a 2D matrix with correct dimensions", {
  pliance_data <- system.file("extdata", "pliance_test.asc", package = "pressuRe")
  obj <- load_pliance(pliance_data)
  expect_true(is.matrix(obj$pressure_array))
  expect_equal(nrow(obj$pressure_array), 281)
  expect_equal(ncol(obj$pressure_array), 179)
})

# ---------------------------------------------------------------------------
# 2C.  Data structure integrity helpers (tekscan)
# ---------------------------------------------------------------------------

test_that("make_pressure_obj produces the expected list structure", {
  tekscan_data <- system.file("extdata", "fscan_testL.asf", package = "pressuRe")
  obj <- load_tekscan(tekscan_data)
  expect_true(is.list(obj))
  for (field in c("pressure_array", "pressure_system", "sens_size",
                  "time", "masks", "events", "sensor_polygons", "max_matrix")) {
    expect_true(field %in% names(obj), info = paste("Field missing:", field))
  }
})

test_that("pressure_array is a 2D matrix with correct dimensions", {
  tekscan_data <- system.file("extdata", "fscan_testL.asf", package = "pressuRe")
  obj <- load_tekscan(tekscan_data)
  expect_true(is.matrix(obj$pressure_array))
  expect_equal(nrow(obj$pressure_array), 205)
  expect_equal(ncol(obj$pressure_array), 942)
})

# ---------------------------------------------------------------------------
# 2C.  Data structure integrity helpers (footscan)
# ---------------------------------------------------------------------------

test_that("make_pressure_obj produces the expected list structure", {
  footscan_data <- system.file("extdata", "footscan_test.xls", package = "pressuRe")
  obj <- load_footscan(footscan_data)
  expect_true(is.list(obj))
  for (field in c("pressure_array", "pressure_system", "sens_size",
                  "time", "masks", "events", "sensor_polygons", "max_matrix")) {
    expect_true(field %in% names(obj), info = paste("Field missing:", field))
  }
})

test_that("pressure_array is a 2D matrix with correct dimensions", {
  footscan_data <- system.file("extdata", "footscan_test.xls", package = "pressuRe")
  obj <- load_footscan(footscan_data)
  expect_true(is.matrix(obj$pressure_array))
  expect_equal(nrow(obj$pressure_array), 329)
  expect_equal(ncol(obj$pressure_array), 387)
})

# ---------------------------------------------------------------------------
# 2C.  Data structure integrity helpers (xsensor)
# ---------------------------------------------------------------------------

#test_that("make_pressure_obj produces the expected list structure", {
#  xsensor_data <- system.file("extdata", "xsensor_data.csv", package = "pressuRe")
#  obj <- load_xsensor(xsensor_data)
#  expect_true(is.list(obj))
#  for (field in c("pressure_array", "pressure_system", "sens_size",
#                  "time", "masks", "events", "sensor_polygons", "max_matrix")) {
#    expect_true(field %in% names(obj), info = paste("Field missing:", field))
#  }
#})

#test_that("pressure_array is a 2D matrix with correct dimensions", {
#  xsensor_data <- system.file("extdata", "xsensor_data.csv", package = "pressuRe")
#  obj <- load_xsensor(xsensor_data)
#  expect_true(is.matrix(obj$pressure_array))
#  expect_equal(nrow(obj$pressure_array), 101)
#  expect_equal(ncol(obj$pressure_array), 458)
#})

# ---------------------------------------------------------------------------
# 2C.  Data structure integrity helpers (stappone)
# ---------------------------------------------------------------------------

test_that("make_pressure_obj produces the expected list structure", {
  stappone_data <- system.file("extdata", "stappone_example.csv", package = "pressuRe")
  obj <- load_stappone(stappone_data)
  expect_true(is.list(obj))
  for (field in c("pressure_array", "pressure_system", "sens_size",
                  "time", "masks", "events", "sensor_polygons", "max_matrix")) {
    expect_true(field %in% names(obj), info = paste("Field missing:", field))
  }
})

test_that("pressure_array is a 2D matrix with correct dimensions", {
  stappone_data <- system.file("extdata", "stappone_example.csv", package = "pressuRe")
  obj <- load_stappone(stappone_data)
  expect_true(is.matrix(obj$pressure_array))
  expect_equal(nrow(obj$pressure_array), 4575)
  expect_equal(ncol(obj$pressure_array), 12)
})

# ---------------------------------------------------------------------------
# 3.  pressure_interp – temporal resampling
# ---------------------------------------------------------------------------

test_that("pressure_interp returns object with resampled pressure_array row count", {
  obj <- make_pressure_obj(n_timepoints = 60)
  out <- pressure_interp(pressure_data = obj, interp_to = 101)
  expect_equal(nrow(out$pressure_array), 101)
})

test_that("pressure_interp preserves the number of sensors (columns)", {
  obj <- make_pressure_obj(n_timepoints = 60, n_sensors = N_SENSORS)
  out <- pressure_interp(pressure_data = obj, interp_to = 101)
  expect_equal(ncol(out$pressure_array), N_SENSORS)
})

test_that("pressure_interp preserves a constant signal exactly", {
  obj <- make_pressure_obj(value = 150, n_timepoints = 60)
  out <- pressure_interp(pressure_data = obj, interp_to = 101)
  expect_true(all(abs(out$pressure_array - 150) < 1e-6))
})

test_that("pressure_interp preserves non-array metadata fields", {
  obj <- make_pressure_obj(n_timepoints = 60)
  out <- pressure_interp(pressure_data = obj, interp_to = 101)
  expect_equal(out$pressure_system, obj$pressure_system)
  expect_equal(out$sens_size,       obj$sens_size)
})

test_that("pressure_interp errors on non-positive n_frames", {
  obj <- make_pressure_obj()
  expect_error(pressure_interp(pressure_data = obj, interp_to = 0))
  expect_error(pressure_interp(pressure_data = obj, interp_to = -5))
})

test_that("pressure_interp errors when pressure_array field is missing", {
  bad <- make_pressure_obj()
  bad$pressure_array <- NULL
  expect_error(pressure_interp(pressure_data = bad, interp_to = 101))
})

# ---------------------------------------------------------------------------
# 4.  whole_pressure_curve – whole-foot summary curves
# ---------------------------------------------------------------------------

test_that("whole_pressure_curve (peak_pressure) returns a numeric vector", {
  obj <- make_pressure_obj()
  out <- whole_pressure_curve(pressure_data = obj, variable = "peak_pressure",
                              plot = FALSE)
  expect_true(is.numeric(out))
})

test_that("whole_pressure_curve vector length equals number of timepoints", {
  obj <- make_pressure_obj(n_timepoints = 80)
  out <- whole_pressure_curve(pressure_data = obj, variable = "peak_pressure",
                              plot = FALSE)
  expect_equal(length(out), 80)
})

test_that("whole_pressure_curve (contact_area) returns a numeric vector", {
  obj <- make_pressure_obj()
  out <- whole_pressure_curve(pressure_data = obj, variable = "area",
                              plot = FALSE)
  expect_true(is.numeric(out))
})

test_that("whole_pressure_curve (force) returns a numeric vector", {
  obj <- make_pressure_obj()
  out <- whole_pressure_curve(pressure_data = obj, variable = "force",
                              plot = FALSE)
  expect_true(is.numeric(out))
})

test_that("whole_pressure_curve peak_pressure is 0 everywhere for zero data", {
  obj <- make_zero_obj()
  out <- whole_pressure_curve(pressure_data = obj, variable = "peak_pressure",
                              plot = FALSE)
  expect_true(all(out == 0))
})

test_that("whole_pressure_curve contact_area is 0 everywhere for zero data", {
  obj <- make_zero_obj()
  out <- whole_pressure_curve(pressure_data = obj, variable = "area",
                              plot = FALSE)
  expect_true(all(out == 0))
})

test_that("whole_pressure_curve peak_pressure matches known constant value", {
  obj <- make_pressure_obj(value = 350)
  out <- whole_pressure_curve(pressure_data = obj, variable = "peak_pressure",
                              plot = FALSE)
  expect_true(all(abs(out - 350) < 1e-6))
})

test_that("whole_pressure_curve peak_pressure is higher for higher pressure", {
  low  <- make_pressure_obj(value = 100)
  high <- make_pressure_obj(value = 400)
  out_low  <- whole_pressure_curve(low,  variable = "peak_pressure", plot = FALSE)
  out_high <- whole_pressure_curve(high, variable = "peak_pressure", plot = FALSE)
  expect_true(all(out_high > out_low))
})

test_that("whole_pressure_curve errors on unknown metric", {
  obj <- make_pressure_obj()
  expect_error(
    whole_pressure_curve(pressure_data = obj, variable = "not_a_metric",
                         plot = FALSE)
  )
})

# ---------------------------------------------------------------------------
# 5.  cop – center of pressure
# ---------------------------------------------------------------------------

test_that("cop returns a two-column data frame or matrix", {
  emed_data <- system.file("extdata", "emed_test.lst", package = "pressuRe")
  obj <- load_emed(emed_data)
  result <- cop(pressure_data = obj)
  expect_true(is.data.frame(result) || is.matrix(result))
  expect_equal(ncol(result), 2)
})

test_that("cop has one row per timepoint", {
  emed_data <- system.file("extdata", "emed_test.lst", package = "pressuRe")
  obj <- load_emed(emed_data)
  result <- cop(pressure_data = obj)
  expect_equal(nrow(result), 106)
})

test_that("cop is stable (same result on repeated calls)", {
  emed_data <- system.file("extdata", "emed_test.lst", package = "pressuRe")
  obj <- load_emed(emed_data)
  expect_equal(cop(obj), cop(obj))
})

test_that("cop errors on non-list input", {
  expect_error(cop(pressure_data = matrix(1, 10, 10)))
})

# ---------------------------------------------------------------------------
# 6.  cpei – center of pressure excursion index
# ---------------------------------------------------------------------------

# test_that("cpei returns a single non-negative numeric scalar", {
#   emed_data <- system.file("extdata", "emed_test.lst", package = "pressuRe")
#   obj <- load_emed(emed_data)
#   result <- cpei(pressure_data = obj)
#   expect_length(result, 1)
#   expect_true(is.numeric(result))
#   expect_gte(result, 0)
# })
#
# test_that("cpei is reproducible", {
#   emed_data <- system.file("extdata", "emed_test.lst", package = "pressuRe")
#   obj <- load_emed(emed_data)
#   expect_equal(cpei(obj), cpei(obj))
# })

# ---------------------------------------------------------------------------
# 7.  mask_analysis – regional pressure metrics
# ---------------------------------------------------------------------------

# Build a simple logical mask: TRUE for first half of sensors
# make_sensor_mask <- function(n_sensors = N_SENSORS) {
#   mask <- rep(FALSE, n_sensors)
#   mask[seq_len(n_sensors %/% 2)] <- TRUE
#   mask
# }

# test_that("mask_analysis returns a data frame or named list", {
#   obj  <- make_pressure_obj()
#   mask <- make_sensor_mask()
#   out  <- mask_analysis(pressure_data = obj, mask = mask)
#   expect_true(is.data.frame(out) || is.list(out))
# })

# test_that("mask_analysis output contains expected metric names", {
#   obj  <- make_pressure_obj()
#   mask <- make_sensor_mask()
#   out  <- mask_analysis(pressure_data = obj, mask = mask)
#   for (metric in c("peak_pressure", "contact_area", "pti")) {
#     expect_true(metric %in% names(out),
#                 info = paste("Expected metric missing:", metric))
#   }
# })

# test_that("mask_analysis peak_pressure is 0 for all-zero data", {
#   obj  <- make_zero_obj()
#   mask <- make_sensor_mask()
#   pp   <- mask_analysis(pressure_data = obj, mask = mask)$peak_pressure
#   expect_equal(pp, 0)
# })

# test_that("mask_analysis contact_area is 0 for all-zero data", {
#   obj  <- make_zero_obj()
#   mask <- make_sensor_mask()
#   ca   <- mask_analysis(pressure_data = obj, mask = mask)$contact_area
#   expect_equal(ca, 0)
# })

# test_that("mask_analysis peak_pressure matches known uniform value", {
#   val  <- 400
#   obj  <- make_pressure_obj(value = val)
#   mask <- make_sensor_mask()
#   pp   <- mask_analysis(pressure_data = obj, mask = mask)$peak_pressure
#   expect_equal(pp, val)
# })

# test_that("mask_analysis pti increases with higher uniform pressure", {
#   low  <- make_pressure_obj(value = 100)
#   high <- make_pressure_obj(value = 500)
#   mask <- make_sensor_mask()
#   expect_lt(mask_analysis(low,  mask)$pti,
#             mask_analysis(high, mask)$pti)
# })

# test_that("mask_analysis sub-region contact_area <= whole-foot contact_area", {
#   obj       <- make_pressure_obj()
#   full_mask <- rep(TRUE, N_SENSORS)
#   sub_mask  <- make_sensor_mask()
#   ca_full   <- mask_analysis(obj, full_mask)$contact_area
#   ca_sub    <- mask_analysis(obj, sub_mask)$contact_area
#   expect_gte(ca_full, ca_sub)
# })


# ---------------------------------------------------------------------------
# 8.  auto_detect_side – left/right foot classification
# ---------------------------------------------------------------------------

test_that("auto_detect_side returns 'RIGHT'", {
  emed_data <- system.file("extdata", "emed_test.lst", package = "pressuRe")
  obj <- load_emed(emed_data)
  result <- auto_detect_side(pressure_data = obj)
  expect_equal(result, "RIGHT")
})

# ---------------------------------------------------------------------------
# 9.  Input validation (shared)
# ---------------------------------------------------------------------------

test_that("whole_pressure_curve errors when pressure_array is missing", {
  bad <- make_pressure_obj()
  bad$pressure_array <- NULL
  expect_error(
    whole_pressure_curve(pressure_data = bad, metric = "peak_pressure",
                         plot = FALSE)
  )
})

test_that("cop errors when pressure_array is missing", {
  bad <- make_pressure_obj()
  bad$pressure_array <- NULL
  expect_error(cop(pressure_data = bad))
})

test_that("pressure_interp errors on non-list input", {
  expect_error(
    pressure_interp(pressure_data = matrix(1, 10, 10), n_frames = 101)
  )
})

# ---------------------------------------------------------------------------
# 10.  Integration / pipeline tests
# ---------------------------------------------------------------------------

# test_that("interp → mask_analysis pipeline is deterministic", {
#   obj  <- make_ramp_obj(n_timepoints = 60)
#   out  <- pressure_interp(obj, interp_to = 101)
#   mask <- make_sensor_mask()
#   res1 <- mask_analysis(out, mask)
#   res2 <- mask_analysis(out, mask)
#   expect_equal(res1$peak_pressure, res2$peak_pressure)
#   expect_equal(res1$pti,           res2$pti)
# })

# test_that("max of whole_pressure_curve equals mask_analysis peak for full mask", {
#   obj       <- make_ramp_obj()
#   pp_curve  <- whole_pressure_curve(obj, metric = "peak_pressure", plot = FALSE)
#   full_mask <- rep(TRUE, N_SENSORS)
#   pp_mask   <- mask_analysis(obj, full_mask)$peak_pressure
#   expect_equal(max(pp_curve), pp_mask, tolerance = 1e-6)
# })

# test_that("pti is proportional to pressure magnitude for uniform data", {
#   dat_1x <- make_pressure_obj(value = 100)
#   dat_3x <- make_pressure_obj(value = 300)
#   mask   <- make_sensor_mask()
#   pti_1x <- mask_analysis(dat_1x, mask)$pti
#   pti_3x <- mask_analysis(dat_3x, mask)$pti
#   expect_equal(pti_3x / pti_1x, 3, tolerance = 0.01)
# })

# test_that("sub-region pti <= whole-foot pti for the same data", {
#   obj       <- make_ramp_obj()
#   full_mask <- rep(TRUE, N_SENSORS)
#   sub_mask  <- make_sensor_mask()
#   pti_full  <- mask_analysis(obj, full_mask)$pti
#   pti_sub   <- mask_analysis(obj, sub_mask)$pti
#   expect_gte(pti_full, pti_sub)
# })

test_that("pressure_interp followed by pressure_interp back is near-identical for smooth data", {
  obj    <- make_ramp_obj(n_timepoints = 101)
  down   <- pressure_interp(obj, interp_to = 51)
  up     <- pressure_interp(down, interp_to = 101)
  # Round-tripping a smooth signal should be close (not exact due to interpolation)
  expect_lt(max(abs(up$pressure_array - obj$pressure_array)), 15)
})
