#' Given a 'SpatialExperiment' data object, create pseudo-bulk
#' samples using the colData information and return a DGEList object
#'
#' @param spe A SpatialExperiment object.
#' @param by.group Logical. Whether to perform pseudo-bulking by group.
#' TRUE by default.
#' @param group.id Character. The column name of the colData(spe) that
#' contains the group information. Default to 'cell_type'.
#' @param keep.groups Vector. Values from group.id to include in pseudo-
#' bulking. Default is NULL, where all cells are included in pseudo-bulking.
#' @param roi Character. The name of the group or cell type on which
#' the roi is computed.
#' If NULL, then no pseudo-bulking will be performed based on roi.
#' Default to NULL.
#' @param roi.only Logical. Whether to filter out pseudo-bulk samples formed
#' by cells not in any ROIs. TRUE by default.
#' @param contour Character. The name of the group or cell type on which
#' the contour level is computed.
#' If NULL, then no pseudo-bulking will be performed based on contour level.
#' Default to NULL.
#'
#' @return An edgeR::DGEList object where each library (column) is a
#' pseudo-bulk sample.
#' @export
#'
#' @examples
#'
#' data("xenium_bc_spe")
#'
#' spe <- gridDensity(spe)
#'
#' coi <- "Breast cancer"
#'
#' spe <- findROI(spe, coi = coi)
#'
#' spe <- allocateCells(spe, to.contour=FALSE)
#'
#' y <- spe2PB(spe, roi = coi)
#'
spe2PB <- function(spe,
                   by.group = TRUE,
                   group.id = "cell_type",
		   keep.groups = NULL, 
                   roi = NULL,
                   roi.only = TRUE,
                   contour = NULL) {
    if (!requireNamespace("SpatialExperiment", quietly = TRUE)) {
        stop("SpatialExperiment is required but is not installed
         (or can't be loaded)")
    }
    
    if (!requireNamespace("edgeR", quietly = TRUE)) {
        stop("edgeR is required but is not installed (or can't be loaded)")
    }
    
    if (!is(spe, "SpatialExperiment")) {
        stop("spe is not of the SpatialExperiment class")
    }

    if (by.group | !is.null(keep.groups)) {
        if (!group.id %in% names(spe@colData)) {
            stop(paste(group.id, "is not found in colData of spe."))
        }
    }

    # subset to only groups we want to keep
    if (!is.null(keep.groups)) {
        if (length(setdiff(keep.groups, spe@colData[, group.id])) > 0.5) {
            stop(paste("Some values in keep.groups do not exist in", group.id))
        }
        kp <- spe@colData[, group.id] %in% keep.groups
        spe <- spe[, kp]
    }

    # Check 'counts'
    counts <- as.matrix(spe@assays@data$counts)
    if (is.null(counts)) stop("spe doesn't contain raw RNA counts")
    
    # Check 'colData'
    cData <- spe@colData
    
    grp <- rois <- clvl <- c()
    
    if (by.group) {
        if (!group.id %in% names(cData)) {
            message(paste(group.id, "is not found in colData of spe. Proceed without group.id."))
        } else {
            grp <- cData[, group.id]
        }
    }
    
    if (!is.null(roi)) {
        roi <- cleanName(roi)
        roi <- paste(c(roi,"roi"), collapse="_")
        if (!roi %in% names(cData)) {
            message(paste(
                roi, " is not found in colData of spe. Proceed without ROIs."
            ))
        } else {
            rois <- paste0("ROI", cData[, roi])
        }
    }
    
    if (!is.null(contour)) {
        
        cont <- cleanName(contour)
        cont <- paste(c(cont,"contour"), collapse="_")
        if (!cont %in% names(cData)) {
            message(paste(
                contour,
                " contour level is not found in colData of spe. Proceed without contour."
            ))
        } else {
            clvl <- paste0("Lv", cData[, cont])
            if (length(table(clvl)) == 1) {
                warning("Only 1 contour level found. Please check
                whether the contour information is specified correctly.")
            }
        }
    }
    
    if (is.null(grp) && is.null(rois) && is.null(clvl)) {
        stop("At least one of the following is required:
        group, ROI, or contour information.")
    }
    
    # Check gene information
    if (ncol(SummarizedExperiment::rowData(spe)) == 0) {
        genes <- data.frame(genes = rownames(spe))
    } else {
        genes <- as.data.frame(SummarizedExperiment::rowData(spe))
    }
    
    # Pseudo-bulk counts
    combo <- c(if (!is.null(grp)) "grp", 
               if (!is.null(rois)) "rois", 
               if (!is.null(clvl)) "clvl")
    eval( parse(text=paste0('group <- paste(', paste(combo, collapse=","), ',sep ="_")') ))
    group <- factor(group)
    # group_mat <- Matrix::sparse.model.matrix(~ 0 + group)
    group_mat <- stats::model.matrix(~ 0 + group)
    colnames(group_mat) <- gsub("^group", "", colnames(group_mat))
    counts.pb <- counts %*% group_mat
    
    # Pseudo-bulk sample information
    sample.pb <- data.frame()[seq_len(ncol(counts.pb)), ]
    sample.pb$n.cells <- as.vector(table(group))
    
    if (!is.null(grp)) {
        grp.pb <- gsub("_ROI.*$", "", levels(group))
        grp.pb <- gsub("_Lv.*$", "", grp.pb)
        sample.pb$group <- grp.pb
    }
    if (!is.null(rois)) {
        rois.pb <- gsub("^.*ROI", "", levels(group))
        rois.pb <- gsub("_.*$", "", rois.pb)
        sample.pb$ROI <- rois.pb
        if (roi.only) keep <- rois.pb != "None"
    }
    if (!is.null(clvl)) {
        clvl.pb <- gsub("^.*Lv", "", levels(group))
        sample.pb$contour.level <- clvl.pb
    }
    
    names(sample.pb) <- gsub("group", group.id, names(sample.pb))
    
    # DGEList
    dge <- edgeR::DGEList(
        counts = as.matrix(counts.pb),
        samples = sample.pb, genes = genes
    )
    if (!is.null(rois) && roi.only) dge <- dge[, keep]
    
    return(dge)
}
