#' Run EnrichR on SCE object
#' @details
#' EnrichR works by querying the specified \code{features} to its online
#' databases, thus it requires the Internet connection.
#'
#' Available \code{db} options could be shown by running
#' \code{enrichR::listEnrichrDbs()$libraryName}
#'
#' This function checks for the existence of features in the SCE object. When
#' \code{features} do not have a match in \code{rownames(inSCE)}, users may
#' try to specify \code{by} to pass the check.
#'
#' EnrichR expects gene symbols/names as the input (i.e. Ensembl ID might not
#' work). When specified \code{features} are not qualified for this, users may
#' try to specify \code{featureName} to change the identifier type to pass to
#' EnrichR.
#' @param inSCE A \linkS4class{SingleCellExperiment} object.
#' @param features Character vector, selected genes for enrichment analysis.
#' @param analysisName A string that identifies each specific analysis.
#' @param db Character vector. Selected database name(s) from the enrichR
#' database list. If \code{NULL} then EnrichR will be run on all the available
#' databases on the enrichR database. See details. Default \code{NULL}
#' @param by Character. From where should we find the \code{features}?
#' \code{"rownames"} for from \code{rownames(inSCE)}, otherwise, from a column
#' of feature metadata (\code{rowData(inSCE)[[by]]}). See details. Default
#' \code{"rownames"}.
#' @param featureName Character. Indicates the actual feature identifiers to be
#' passed to EnrichR. Can be \code{"rownames"}, a column in feature metadata
#' (\code{rowData(inSCE)[[featureName]]}), or a character vector with its length
#' equals to \code{nrow(inSCE)}. See details. Default \code{"rownames"}.
#' @return Updates \code{inSCE} metadata with a data.frame of enrichment terms
#' overlapping in the respective databases along with p-values, z-scores etc.
#' @export
#' @seealso \code{\link{getEnrichRResult}}
#' @examples
#' data("mouseBrainSubsetSCE")
#' if (Biobase::testBioCConnection()) {
#'   mouseBrainSubsetSCE <- runEnrichR(mouseBrainSubsetSCE, features =  c("Vamp7","Cntn2","Olig1"),
#'                                     db = "GO_Cellular_Component_2025",
#'                                     analysisName = "analysis1")
#' }
#'
runEnrichR <- function(inSCE,
                       features,
                       analysisName,
                       db = NULL,
                       by = "rownames",
                       featureName = NULL) {
  if (!inherits(inSCE, "SingleCellExperiment")) {
    stop("inSCE has to inherit from SingleCellExperiment object.")
  }
  if (is.null(analysisName)) {
    stop("Have to specify analysisName.")
  }
  if (by == "rownames") {
    if (!all(features %in% rownames(inSCE))) {
      stop("Not all features found in `rownames(inSCE)`.")
    }
    allFeatures <- rownames(inSCE)
  } else {
    if (!by %in% names(SummarizedExperiment::rowData(inSCE))) {
      stop("`by` not found in rowData(inSCE).")
    }
    if (!all(features %in% SummarizedExperiment::rowData(inSCE)[[by]])) {
      stop("Not all features found in `rowData(inSCE)$",by,"`.")
    }
    allFeatures <- SummarizedExperiment::rowData(inSCE)[[by]]
  }
  if (!is.null(featureName)) {
    featureIdx <- allFeatures %in% features
    if (length(featureName) == 1) {
      if (featureName == "rownames") {
        features <- rownames(inSCE)[featureIdx]
      } else if (featureName %in% names(SummarizedExperiment::rowData(inSCE))) {
        features <- SummarizedExperiment::rowData(inSCE[featureIdx,])[[featureName]]
      } else {
        stop("featureName not found in `rowData(inSCE)`.")
      }
    } else if (length(featureName) == nrow(inSCE)) {
      features <- featureName[featureIdx]
    } else {
      stop("Invalid featureName specification.")
    }
  }
  internetConnection <- suppressWarnings(Biobase::testBioCConnection())
  #check for internet connection
  if (!internetConnection){
    stop("The Enrichr database could not be queried. No internet connection available.")
  }
  err <- tryCatch(
    {
      attachNamespace("enrichR")
    },
    error = function(e) {}
  )
  #options(enrichR.base.address = "https://maayanlab.cloud/Enrichr/")
  #options(enrichR.live = TRUE)
  temp_db <- NULL
  err <- tryCatch(
    {
      temp_db <- .getEnrichrDB(noConnect = "error")
    },
    error = function(e) {}
  )
  enrdb <- temp_db$libraryName
  #test for db existing
  if (is.null(db)){
    db <- enrdb
  } else if (!all(db %in% enrdb)){
    db.notFound <- db[!db %in% enrdb]
    stop("Database(s) ", paste(db.notFound, collapse = ", "), " were not found in Enrichr.")
  }

  enriched <- enrichR::enrichr(features, db)
  enriched <- data.frame(data.table::rbindlist(enriched, use.names = TRUE,
                                               fill = TRUE,
                                               idcol = "Database_selected"))

  if(!is.null(enriched) && nrow(enriched) > 0) {

    #sort the results based on p-values
    enriched <- enriched[order(enriched$P.value, decreasing = FALSE), ]

    getEnrichRResult(inSCE, analysisName) <- list(result = enriched,
                                                  param = list(
                                                    features = features,
                                                    by = by,
                                                    db = db
                                                  ))
  } else {
    stop("No enrichments identified. No enrichment results were created.")
  }

  return(inSCE)
}

#' @title Plot EnrichR results
#' @description Plot results of EnrichR analysis as a barplot.
#' @param inSCE Input \linkS4class{SingleCellExperiment} object with saved
#' EnrichR results. Required.
#' @param analysisName A string that identifies the specific analysis to plot.
#' Required.
#' @param showTerms Number of enrichment terms to show. Default is 20.
#' @param numChar Integer. Indicates the maximum number characters to be
#' displayed in the term names. Default is 40.
#' @param y Character string. Indicates the variable to be shown on the
#' y-axis. Can be one of "Count" or "Ratio". Results that include background
#' genes can only show "Count". Default is "Count".
#' @param orderBy Charachter string. Indicates how to order the results prior
#' to subsetting. Can be one of "P.value", "Adjusted.P.Value",
#' or "Combined.Score". Default is "Adjusted.P.value".
#' @param xlab Character vector. Label for x-axis. Default NULL.
#' @param ylab Character vector. Label for y-axis. Default NULL.
#' @param title Character string. Title of the plot. Default NULL.
#' @return A ggplot object of EnrichR results.
#' @export
plotEnrichR <- function(inSCE,
                        analysisName,
                        showTerms = 20,
                        numChar = 40,
                        y = "Count",
                        orderBy = "Adjusted.P.value",
                        xlab = NULL,
                        ylab = NULL,
                        title = NULL){
    res <- getEnrichRResult(inSCE, analysisName = analysisName)
    p <- enrichR::plotEnrich(df = res$result, showTerms = showTerms, numChar = numChar, y = y,
                    orderBy = orderBy, xlab = xlab, ylab = ylab, title = title)
    return(p)
}


#' @title Get or Set EnrichR Result
#' @rdname getEnrichRResult
#' @param inSCE A \linkS4class{SingleCellExperiment} object.
#' @param analysisName A string that identifies each specific analysis
#' @param value The EnrichR result table
#' @return For getter method, a data.frame of the EnrichR result;
#' For setter method, \code{inSCE} with EnrichR results updated.
#' @export
#' @seealso \code{\link{runEnrichR}}
#' @examples
#' data("mouseBrainSubsetSCE")
#' if (Biobase::testBioCConnection()) {
#'   mouseBrainSubsetSCE <- runEnrichR(mouseBrainSubsetSCE, features = c("Vamp7","Cntn2","Olig1"),
#'                                     db = "GO_Cellular_Component_2025",
#'                                     analysisName = "analysis1")
#'   result <- getEnrichRResult(mouseBrainSubsetSCE, "analysis1")
#' }
setGeneric("getEnrichRResult<-", function(inSCE, analysisName, value)
  standardGeneric("getEnrichRResult<-"))

#' @rdname getEnrichRResult
#' @export
setGeneric("getEnrichRResult", function(inSCE, analysisName)
  standardGeneric("getEnrichRResult"))

#' @rdname getEnrichRResult
#' @export
setMethod("getEnrichRResult",
          "SingleCellExperiment",
          function(inSCE, analysisName){
            if (!"runEnrichR" %in% names(S4Vectors::metadata(inSCE)$sctk)) {
              stop("EnrichR analysis not performed yet. ",
                   "Please run `runEnrichR()`")
            }
            if (!analysisName %in% names(S4Vectors::metadata(inSCE)$sctk$runEnrichR)) {
              stop('"', analysisName, '" not found in EnrichR analysis names.')
            }
            return(S4Vectors::metadata(inSCE)$sctk$runEnrichR[[analysisName]])
          })

#' @rdname getEnrichRResult
#' @export
setReplaceMethod("getEnrichRResult",
                 c("SingleCellExperiment"),
                 function(inSCE, analysisName, value) {
                   if (is.null(analysisName)) {
                     stop("Have to specify analysisName.")
                   }
                   S4Vectors::metadata(inSCE)$sctk$runEnrichR[[analysisName]] <- value
                   return(inSCE)
                 })

.getEnrichrDB <- function(noConnect = c("warn", "error", "ignore")) {
  db <- NULL
  noConnect <- match.arg(noConnect)
  db <- enrichR::listEnrichrDbs()
  if(is.null(db)) {
    if(noConnect == "error") {
      stop("Connection to the online Enrichr database could not be established. No database information retrieved.")
    } else if (noConnect == "warn") {
      warning("Connection to the online Enrichr database could not be established. No database information retrieved.")
    }
  }
  return(db)
}
