TestDensities <-
function(Z, y, X = NULL, outcomeType = "C", histBreaks = 200, lambdas = c(0, exp(-10:10)), kernel = "linear", hideProgress = FALSE, adjustmentDichot = FALSE, knotLocs = c(seq(0,0.3, by = 0.02), seq(0.32, 0.62, by = 0.10), seq(0.72, 1, by = 0.02))) {
  ## Required packages
  require(fda)
  require(SKAT)

  if (class(Z) !="matrix") stop("Z must be a matrix!")
  if (ncol(Z) != length(y)) stop("Dimensions of Z and y do not match!")
  if (!is.null(X)) {
    if (class(X) !="matrix") stop("X must be a matrix!")
    if (nrow(X) != length(y)) stop("Dimensions of X and y do not match!")
  }
  
  ## helper functions
  getHist = function(x, breaks) {
    ## gets the histogram intensities
    ints = hist(x, breaks = breaks, plot = F)$intensities
    return(ints)
  }
  getGCV = function(lambda,  H1, basis,argvals) {
    ## computes the GCVs across a range of lambdas
    fdP = fdPar(basis, lambda=lambda)
    F.h1= smooth.basis(argvals, H1, fdP)
    return(F.h1$gcv)
  }

  
  ## obtain histogram
  if (!hideProgress) print("1. Estimating Histograms")
  breaks.h=seq(0,1, length = histBreaks) 
  H = apply(Z,2, getHist, breaks.h)

  ## obtain basis
  if (!hideProgress) print("2. Computing Bases")
  breaks.b = knotLocs
  basis = create.bspline.basis(norder = 4, breaks =breaks.b)
  vals  = 0.5*(breaks.h[-1] + breaks.h[-histBreaks])
  
  ## find best lambda
  if (!hideProgress) print("3. Finding Optimal Lambda")
  if (length(lambdas) > 1){
    gcvs = sapply(lambdas, getGCV, H, basis, vals)
    avgGCV = apply(gcvs, 2, mean)
    lambda = lambdas[which.min(avgGCV)]
  } else {
    lambda = lambdas
  }

  ## smooth data using best lambda
  if (!hideProgress) print("4. Obtaining B-spline coefficients")
  fdP = fdPar(basis, lambda=lambda)
  F.h= smooth.basis(vals, H, fdP)$fd
  Z1 = coef(F.h)

  ## test coefficients using kernel machine test
  if (!hideProgress) print("5. Testing")
  if (!is.null(X)){
    nullMod = SKAT_Null_Model(y~X, out_type = outcomeType, Adjustment = adjustmentDichot)
  } else {
    nullMod = SKAT_Null_Model(y~1, out_type = outcomeType, Adjustment = adjustmentDichot)
  }
  res = SKAT(t(Z1), nullMod, is_check_genotype = F, kernel = kernel, method = "liu")

  return(res$p.value)
}
