# Helper functions to get Seurat object or package version numbers
.getSeuratObjectMajorVersion <- function(seuratObject){
    if(!methods::is(seuratObject, "Seurat")) {
        stop("The object was not a 'Seurat' object but was of class: ", class(seuratObject))
    }
    v <- slot(seuratObject, "version")$major
    return(v)
}

.getSeuratPackageVersion <- function(){
    v <- packageVersion(pkg = "SeuratObject")$major
    return(v)
}

# Helper/Wrapper Functions ---

#' .getComponentNames
#' Creates a list of PC/IC components to populate the picker for PC/IC heatmap
#' generation
#' @param maxComponents Number of components to return for the picker
#' @param component Which component to use. Choices are \code{PC} or \code{IC}.
#' @return List of component names (appended with \code{PC} or \code{IC})
#' @noRd
.getComponentNames <-
  function(maxComponents, component = c("PC", "IC")) {
    componentNames <- list()
    for (i in seq(maxComponents)) {
      componentNames[i] <- paste0(component, i)
    }
    return(componentNames)
  }

#' .getSeuratObject
#' Retrieves the Seurat object stored in the input SCE, if exists
#' @param inSCE (sce) object containing the Seurat data, located
#' in inSCE\@\metadata$seurat$obj
#' @return the Seurat object if it exists
#' @noRd
.getSeuratObject <- function(inSCE) {
  return(metadata(inSCE)$seurat$obj)
}

#' .addSeuratToMetaDataSCE
#' Adds the input seurat object to the metadata slot of the input sce object
#' (after removing the data matrices)
#' @param inSCE (sce) object to which seurat object should be added in the
#' metadata slot (copy to)
#' @param seuratObject seurat object which should be added to the metadata slot
#' of sce object (copy from)
#' @return Updated \code{SingleCellExperiment} object which now contains the
#' seurat object in its metadata slot (excluding data matrices)
#' @noRd
.addSeuratToMetaDataSCE <- function(inSCE, seuratObject) {
  seurat.version <- .getSeuratObjectMajorVersion(seuratObject)

  if(seurat.version >= 5.0){

    seuratObject@assays$RNA@layers$counts <- methods::new("dgCMatrix")
    seuratObject@assays$RNA@layers$data <- methods::new("dgCMatrix")
    seuratObject@assays$RNA@layers$scale.data <- methods::new("dgCMatrix")
    inSCE@metadata$seurat$obj <- seuratObject

    # add var features if exists
    #if (!is.null(Seurat::VariableFeatures(seuratObject)) && length(Seurat::VariableFeatures(seuratObject)) > 0) {
    #  inSCE@metadata$seurat$obj@assays$RNA$"var.features" <-
    #    Seurat::VariableFeatures(object = seuratObject)
    #}

    #inSCE@metadata$seurat$obj@assays$RNA@layers$counts <- methods::new("dgCMatrix")
    #inSCE@metadata$seurat$obj@assays$RNA@layers$data <- methods::new("dgCMatrix")
    #inSCE@metadata$seurat$obj@assays$RNA@layers$scale.data <- matrix()

    # Determine if slot is called "meta.data" or "meta.features"
      if("meta.data" %in% methods::slotNames(seuratObject@assays$RNA)) {
         inSCE@metadata$seurat$obj@assays$RNA@meta.data  <- seuratObject@assays$RNA@meta.data
      } else if ("meta.features" %in% methods::slotNames(seuratObject@assays$RNA)) {
         inSCE@metadata$seurat$obj@assays$RNA@meta.features  <- seuratObject@assays$RNA@meta.features
      }

    inSCE@metadata$seurat$obj@meta.data <- seuratObject@meta.data

    inSCE@metadata$seurat$obj@commands <- seuratObject@commands

    inSCE@metadata$seurat$obj@reductions$pca <- seuratObject@reductions$pca
    inSCE@metadata$seurat$obj@reductions$ica <- seuratObject@reductions$ica
    inSCE@metadata$seurat$obj@reductions$tsne <- seuratObject@reductions$tsne
    inSCE@metadata$seurat$obj@reductions$umap <- seuratObject@reductions$umap

  }
  else{
    seuratObject@assays$RNA@counts <- methods::new("dgCMatrix")
    seuratObject@assays$RNA@data <- methods::new("dgCMatrix")
    seuratObject@assays$RNA@scale.data <- matrix()
    inSCE@metadata$seurat$obj <- seuratObject
  }

  return(inSCE)
}

#' .computeSignificantPC
#' Computes the significant principal components from an input sce object (must
#' contain pca slot) using stdev
#' @param inSCE (sce) object with pca computed
#' @return A numerical value indicating how many number of components are
#' considered significant
#' @noRd
.computeSignificantPC <- function(inSCE) {
  seuratObject <- convertSCEToSeurat(inSCE)
  max_components <- 0
  currentDistance <- 0
  previousDistance <- 0
  for (i in seq(seuratObject[["pca"]]@stdev)[-1] - 1) {
    currentDistance <- abs(seuratObject[["pca"]]@stdev[i + 1] -
                             seuratObject[["pca"]]@stdev[i])
    if (abs(currentDistance - previousDistance) > 0.01) {
      previousDistance <- currentDistance
      max_components <- i
    }
    else{
      break
    }
  }
  return(max_components)
}

#' runSeuratNormalizeData
#' Wrapper for NormalizeData() function from seurat library
#' Normalizes the sce object according to the input parameters
#' @param inSCE (sce) object to normalize
#' @param useAssay Assay containing raw counts to use for normalization.
#' @param normAssayName Name of new assay containing normalized data. Default
#' \code{seuratNormData}.
#' @param normalizationMethod selected normalization method. Default
#' \code{"LogNormalize"}.
#' @param scaleFactor numeric value that represents the scaling factor. Default
#' \code{10000}.
#' @param verbose Logical value indicating if informative messages should
#'  be displayed. Default is \code{TRUE}.
#' @examples
#' data(scExample, package = "singleCellTK")
#' \dontrun{
#' sce <- runSeuratNormalizeData(sce, useAssay = "counts")
#' }
#' @return Normalized \code{SingleCellExperiment} object
#' @export
runSeuratNormalizeData <- function(inSCE,
                                   useAssay,
                                   normAssayName = "seuratNormData",
                                   normalizationMethod = "LogNormalize",
                                   scaleFactor = 10000,
                                   verbose = TRUE) {
  if (missing(useAssay)) {
    useAssay <- SummarizedExperiment::assayNames(inSCE)[1]
    message(
      "'useAssay' parameter missing. Using the first available assay ",
      "instead: '",
      useAssay,
      "'"
    )
  }
  seuratObject <-
    Seurat::NormalizeData(
      convertSCEToSeurat(inSCE, useAssay),
      normalization.method = normalizationMethod,
      scale.factor = scaleFactor,
      verbose = verbose
    )
  inSCE <-
    .updateAssaySCE(inSCE, seuratObject, normAssayName, "data")
  inSCE <- .addSeuratToMetaDataSCE(inSCE, seuratObject)
  inSCE@metadata$seurat$normAssay <- normAssayName
  inSCE <- expSetDataTag(inSCE = inSCE,
                         assayType = "normalized",
                         assays = normAssayName)
  return(inSCE)
}

#' runSeuratScaleData
#' Scales the input sce object according to the input parameters
#' @param inSCE (sce) object to scale
#' @param useAssay Assay containing normalized counts to scale.
#' @param scaledAssayName Name of new assay containing scaled data. Default
#' \code{seuratScaledData}.
#' @param model selected model to use for scaling data. Default \code{"linear"}.
#' @param scale boolean if data should be scaled or not. Default \code{TRUE}.
#' @param center boolean if data should be centered or not. Default \code{TRUE}
#' @param scaleMax maximum numeric value to return for scaled data. Default
#' \code{10}.
#' @param verbose Logical value indicating if informative messages should
#'  be displayed. Default is \code{TRUE}.
#' @examples
#' data(scExample, package = "singleCellTK")
#' \dontrun{
#' sce <- runSeuratNormalizeData(sce, useAssay = "counts")
#' sce <- runSeuratFindHVG(sce, useAssay = "counts")
#' sce <- runSeuratScaleData(sce, useAssay = "counts")
#' }
#' @return Scaled \code{SingleCellExperiment} object
#' @export
runSeuratScaleData <- function(inSCE,
                               useAssay = "seuratNormData",
                               scaledAssayName = "seuratScaledData",
                               model = "linear",
                               scale = TRUE,
                               center = TRUE,
                               scaleMax = 10,
                               verbose = TRUE) {
  seuratObject <- convertSCEToSeurat(inSCE, useAssay)
  seuratObject <- Seurat::ScaleData(
    seuratObject,
    features = getSeuratVariableFeatures(inSCE),
    model.use = model,
    do.scale = scale,
    do.center = center,
    scale.max = as.double(scaleMax),
    verbose = verbose
  )
  inSCE <-
    .updateAssaySCE(inSCE, seuratObject, scaledAssayName, "scale.data")
  inSCE <- .addSeuratToMetaDataSCE(inSCE, seuratObject)
  # inSCE@metadata$seurat$scaledAssay <- scaledAssayName
  # inSCE <- expSetDataTag(inSCE = inSCE, assayType = "scaled",
  #                        assays = scaledAssayName)
  return(inSCE)
}

#' runSeuratFindHVG
#' Find highly variable genes and store in the input sce object
#' @param inSCE (sce) object to compute highly variable genes from and to store
#' back to it
#' @param useAssay Specify the name of the assay to use for computation
#'  of variable genes. It is recommended to use a raw counts assay with the
#'  \code{"vst"} method and normalized assay with all other methods. Default
#'  is \code{"counts"}.
#' @param method selected method to use for computation of highly variable
#' genes. One of \code{'vst'}, \code{'dispersion'}, or \code{'mean.var.plot'}.
#' Default \code{"vst"} which uses the raw counts. All other methods use
#' normalized counts.
#' @param hvgNumber numeric value of how many genes to select as highly
#' variable. Default \code{2000}
#' @param createFeatureSubset Specify a name of the subset to create
#' for the identified variable features. Default is \code{"hvf"}.
#' Leave it \code{NULL} if you do not want to create a subset of
#' variable features.
#' @param altExp Logical value indicating if the input object is an
#' altExperiment. Default \code{FALSE}.
#' @param verbose Logical value indicating if informative messages should
#'  be displayed. Default is \code{TRUE}.
#' @examples
#' data(scExample, package = "singleCellTK")
#' sce <- runSeuratFindHVG(sce)
#' @return Updated \code{SingleCellExperiment} object with highly variable genes
#' computation stored
#' @seealso \code{\link{runFeatureSelection}}, \code{\link{runModelGeneVar}},
#' \code{\link{getTopHVG}}, \code{\link{plotTopHVG}}
#' @export
#' @importFrom SummarizedExperiment rowData rowData<-
#' @importFrom S4Vectors metadata<-
runSeuratFindHVG <- function(inSCE,
                             useAssay = "counts",
                             method = c("vst", "dispersion", "mean.var.plot"),
                             hvgNumber = 2000,
                             createFeatureSubset = "hvf",
                             altExp = FALSE,
                             verbose = TRUE) {
  method <- match.arg(method)
  seurat.pkg.version <- .getSeuratPackageVersion()
  if(seurat.pkg.version >= 5.0){
    seuratObject <- convertSCEToSeurat(inSCE, countsAssay = useAssay)
  }
  else{
    if (method == "vst") {
      seuratObject <- convertSCEToSeurat(inSCE, countsAssay = useAssay)
    } else {
      seuratObject <- convertSCEToSeurat(inSCE, normAssay = useAssay)
    }
  }

  # Get version number of object
  seurat.version <- .getSeuratObjectMajorVersion(seuratObject)

  seuratObject <- Seurat::FindVariableFeatures(
    seuratObject,
    selection.method = method,
    nfeatures = hvgNumber,
    verbose = verbose
  )
  inSCE <- .addSeuratToMetaDataSCE(inSCE, seuratObject)
  if (method == "vst") {
    if(seurat.version >= 5.0){
      cn <- colnames(seuratObject@assays$RNA@meta.data)
      if (!altExp) {
        rowData(inSCE)$seurat_variableFeatures_vst_varianceStandardized <-
          unlist(seuratObject@assays$RNA@meta.data["vf_vst_counts_variance.standardized"])
        rowData(inSCE)$seurat_variableFeatures_vst_mean <-
          unlist(seuratObject@assays$RNA@meta.data["vf_vst_counts_mean"])
        rowData(inSCE)[,cn] <- seuratObject@assays$RNA@meta.data
      }
      else{
        # remove this part of code when updating to ExperimentSubset and add the
        # above code in if clause as the complete code
        altExpRows <-
          match(rownames(inSCE),
                seuratObject@assays$RNA@meta.data["var.features"][,1]
          )
        rowData(inSCE)$seurat_variableFeatures_vst_varianceStandardized <-
          unlist(
            seuratObject@assays$RNA@meta.data["vf_vst_counts_variance.standardized"][[1]][altExpRows])
        rowData(inSCE)$seurat_variableFeatures_vst_mean <-
          unlist(
            seuratObject@assays$RNA@meta.data["vst.mean"][[1]][altExpRows])
        rowData(inSCE)[,cn] <- seuratObject@assays$RNA@meta.data[altExpRows,]
      }
    }
    else{
      cn <- colnames(seuratObject@assays$RNA@meta.features)
      if (!altExp) {
        rowData(inSCE)$seurat_variableFeatures_vst_varianceStandardized <-
          methods::slot(inSCE@metadata$seurat$obj, "assays")[["RNA"]]@meta.features$vst.variance.standardized
        rowData(inSCE)$seurat_variableFeatures_vst_mean <-
          methods::slot(inSCE@metadata$seurat$obj, "assays")[["RNA"]]@meta.features$vst.mean
        rowData(inSCE)[,cn] <- seuratObject@assays$RNA@meta.features
      }
      else{
        # remove this part of code when updating to ExperimentSubset and add the
        # above code in if clause as the complete code
        altExpRows <-
          match(rownames(inSCE),
                rownames(
                  methods::slot(inSCE@metadata$seurat$obj, "assays")[["RNA"]]@meta.features
                ))
        rowData(inSCE)$seurat_variableFeatures_vst_varianceStandardized <-
          methods::slot(inSCE@metadata$seurat$obj, "assays")[["RNA"]]@meta.features$vst.variance.standardized[altExpRows]
        rowData(inSCE)$seurat_variableFeatures_vst_mean <-
          methods::slot(inSCE@metadata$seurat$obj, "assays")[["RNA"]]@meta.features$vst.mean[altExpRows]
        rowData(inSCE)[,cn] <- seuratObject@assays$RNA@meta.features[altExpRows,]
      }
    }

    metadata(inSCE)$sctk$runFeatureSelection$vst <-
      list(
        useAssay = useAssay,
        rowData = c(
          "seurat_variableFeatures_vst_varianceStandardized",
          "seurat_variableFeatures_vst_mean"
        )
      )
  } else if (method == "dispersion") {
    if(seurat.version >= 5.0){
      cn <- colnames(seuratObject@assays$RNA@meta.data)
      rowData(inSCE)$seurat_variableFeatures_dispersion_dispersion <-
        unlist(seuratObject@assays$RNA@meta.data["vf_vst_counts_variance.standardized"])
      rowData(inSCE)$seurat_variableFeatures_dispersion_dispersionScaled <-
        unlist(seuratObject@assays$RNA@meta.data["vf_vst_counts_variance.standardized"])
      rowData(inSCE)$seurat_variableFeatures_dispersion_mean <-
        unlist(seuratObject@assays$RNA@meta.data["vf_vst_counts_variance.standardized"])
      rowData(inSCE)[,cn] <- seuratObject@assays$RNA@meta.data
    }
    else{
      cn <- colnames(seuratObject@assays$RNA@meta.features)
      rowData(inSCE)$seurat_variableFeatures_dispersion_dispersion <-
        methods::slot(inSCE@metadata$seurat$obj, "assays")[["RNA"]]@meta.features$mvp.dispersion
      rowData(inSCE)$seurat_variableFeatures_dispersion_dispersionScaled <-
        methods::slot(inSCE@metadata$seurat$obj, "assays")[["RNA"]]@meta.features$mvp.dispersion.scaled
      rowData(inSCE)$seurat_variableFeatures_dispersion_mean <-
        methods::slot(inSCE@metadata$seurat$obj, "assays")[["RNA"]]@meta.features$mvp.mean
      rowData(inSCE)[,cn] <- seuratObject@assays$RNA@meta.features
    }
    metadata(inSCE)$sctk$runFeatureSelection$dispersion <-
      list(
        useAssay = useAssay,
        rowData = c(
          "seurat_variableFeatures_dispersion_dispersion",
          "seurat_variableFeatures_dispersion_dispersionScaled",
          "seurat_variableFeatures_dispersion_mean"
        )
      )
  }
  else if (method == "mean.var.plot") {
    if(seurat.version >= 5.0){
      cn <- colnames(seuratObject@assays$RNA@meta.data)
      rowData(inSCE)$seurat_variableFeatures_mvp_dispersion <-
        unlist(seuratObject@assays$RNA@meta.data["vf_vst_counts_variance.standardized"])
      rowData(inSCE)$seurat_variableFeatures_mvp_dispersionScaled <-
        unlist(seuratObject@assays$RNA@meta.data["vf_vst_counts_variance.standardized"])
      rowData(inSCE)$seurat_variableFeatures_mvp_mean <-
        unlist(seuratObject@assays$RNA@meta.data["vf_vst_counts_variance.standardized"])
      rowData(inSCE)[,cn] <- seuratObject@assays$RNA@meta.data
    }
    else{
      cn <- colnames(seuratObject@assays$RNA@meta.features)
      rowData(inSCE)$seurat_variableFeatures_mvp_dispersion <-
        methods::slot(inSCE@metadata$seurat$obj, "assays")[["RNA"]]@meta.features$mvp.dispersion
      rowData(inSCE)$seurat_variableFeatures_mvp_dispersionScaled <-
        methods::slot(inSCE@metadata$seurat$obj, "assays")[["RNA"]]@meta.features$mvp.dispersion.scaled
      rowData(inSCE)$seurat_variableFeatures_mvp_mean <-
        methods::slot(inSCE@metadata$seurat$obj, "assays")[["RNA"]]@meta.features$mvp.mean
      rowData(inSCE)[,cn] <- seuratObject@assays$RNA@meta.features
    }
   metadata(inSCE)$sctk$runFeatureSelection$mean.var.plot <-
      list(
        useAssay = useAssay,
        rowData = c(
          "seurat_variableFeatures_mvp_dispersion",
          "seurat_variableFeatures_mvp_dispersionScaled",
          "seurat_variableFeatures_mvp_mean"
        )
      )
  }

  # create a feature subset
  if(!is.null(createFeatureSubset)){
    inSCE <- setTopHVG(inSCE = inSCE,
                       method = method,
                       hvgNumber = hvgNumber,
                       featureSubsetName = createFeatureSubset)
  }

  return(inSCE)
}

#' runSeuratPCA
#' Computes PCA on the input sce object and stores the calculated principal
#' components within the sce object
#' @param inSCE (sce) object on which to compute PCA
#' @param useAssay Assay containing scaled counts to use in PCA. Default
#' \code{"seuratNormData"}.
#' @param reducedDimName Name of new reducedDims object containing Seurat PCA.
#' Default \code{seuratPCA}.
#' @param nPCs numeric value of how many components to compute. Default
#' \code{20}.
#' @param useFeatureSubset Subset of feature to use for dimension reduction. A
#' character string indicating a \code{rowData} variable that stores the logical
#' vector of HVG selection, or a vector that can subset the rows of
#' \code{inSCE}. Default \code{"hvf"}.
#' @param scale Logical scalar, whether to standardize the expression values
#' using \code{\link[Seurat]{ScaleData}}. Default \code{TRUE}.
#' @param seed Random seed for reproducibility of results.
#' Default \code{NULL} will use global seed in use by the R environment.
#' @param verbose Logical value indicating if informative messages should
#'  be displayed. Default is \code{TRUE}.
#' @details
#' For features used for computation, it can be controlled by \code{features} or
#' \code{useFeatureSubset}. When \code{features} is specified, the scaling and
#' dimensionality reduction will only be processed with these features. When
#' \code{features} is \code{NULL} but \code{useFeatureSubset} is specified, will
#' use the features that the HVG list points to. If both parameters are
#' \code{NULL}, the function will see if any Seurat's variable feature detection
#' has been ever performed, and use them if found. Otherwise, all features are
#' used.
#' @examples
#' data(scExample, package = "singleCellTK")
#' \dontrun{
#' sce <- runSeuratNormalizeData(sce, useAssay = "counts")
#' sce <- runSeuratFindHVG(sce, useAssay = "counts")
#' sce <- setTopHVG(sce, method = "vst", featureSubsetName = "hvf")
#' sce <- runSeuratScaleData(sce, useAssay = "counts")
#' sce <- runSeuratPCA(sce, useAssay = "counts")
#' }
#' @return Updated \code{SingleCellExperiment} object which now contains the
#' computed principal components
#' @export
#' @importFrom SingleCellExperiment reducedDim<- rowSubset
#' @importFrom S4Vectors metadata<-
runSeuratPCA <-
  function(inSCE,
           useAssay = "seuratNormData",
           useFeatureSubset = "hvf",
           scale = TRUE,
           reducedDimName = "seuratPCA",
           nPCs = 20,
           seed = 12345,
           verbose = TRUE) {
    params <- as.list(environment())
    params$inSCE <- NULL
    if (!isTRUE(scale)) {
      # If not doing a scaling, put useAssay as scaled as RunPCA need it
      seuratObject <-
        convertSCEToSeurat(inSCE, scaledAssay = useAssay)
    } else {
      # If doing scaling, put useAssay as normed, used by ScaleData first
      seuratObject <- convertSCEToSeurat(inSCE, normAssay = useAssay)
    }

    features <- .parseUseFeatureSubset(inSCE, useFeatureSubset,
                                       altExpObj = NULL,
                                       returnType = "character")
    if (is.null(features)) {
      if (length(Seurat::VariableFeatures(seuratObject)) == 0) {
        features <- rownames(inSCE)
      }
    }

    if (isTRUE(scale)) {
      seuratObject <-
        Seurat::ScaleData(seuratObject, features = features,
                          verbose = verbose)
    }

    seuratObject <- Seurat::RunPCA(
      seuratObject,
      npcs = as.double(nPCs),
      verbose = verbose,
      features = features,
      seed.use = seed
    )
    inSCE <- .addSeuratToMetaDataSCE(inSCE, seuratObject)

    temp <- seuratObject@reductions$pca@cell.embeddings
    rownames(temp) <- colnames(inSCE)
    reducedDim(inSCE, reducedDimName) <- temp
    metadata(inSCE)$sctk$runDimReduce$reddim[[reducedDimName]] <- params
    return(inSCE)
  }

#' runSeuratICA
#' Computes ICA on the input sce object and stores the calculated independent
#' components within the sce object
#' @param inSCE (sce) object on which to compute ICA
#' @param useAssay Assay containing scaled counts to use in ICA.
#' @param reducedDimName Name of new reducedDims object containing Seurat ICA
#' Default \code{seuratICA}.
#' @param useFeatureSubset Subset of feature to use for dimension reduction. A
#' character string indicating a \code{rowData} variable that stores the logical
#' vector of HVG selection, or a vector that can subset the rows of
#' \code{inSCE}. Default \code{NULL}.
#' @param scale Logical scalar, whether to standardize the expression values
#' using \code{\link[Seurat]{ScaleData}}. Default \code{TRUE}.
#' @param nics Number of independent components to compute. Default \code{20}.
#' @param seed Random seed for reproducibility of results.
#' Default \code{NULL} will use global seed in use by the R environment.
#' @param verbose Logical value indicating if informative messages should
#'  be displayed. Default is \code{TRUE}.
#' @details
#' For features used for computation, it can be controlled by \code{features} or
#' \code{useFeatureSubset}. When \code{features} is specified, the scaling and
#' dimensionality reduction will only be processed with these features. When
#' \code{features} is \code{NULL} but \code{useFeatureSubset} is specified, will
#' use the features that the HVG list points to. If both parameters are
#' \code{NULL}, the function will see if any Seurat's variable feature detection
#' has been ever performed, and use them if found. Otherwise, all features are
#' used.
#' @examples
#' data(scExample, package = "singleCellTK")
#' \dontrun{
#' sce <- runSeuratNormalizeData(sce, useAssay = "counts")
#' sce <- runSeuratFindHVG(sce, useAssay = "counts")
#' sce <- runSeuratScaleData(sce, useAssay = "counts")
#' sce <- runSeuratICA(sce, useAssay = "counts")
#' }
#' @return Updated \code{SingleCellExperiment} object which now contains the
#' computed independent components
#' @export
#' @importFrom SingleCellExperiment reducedDim<- rowSubset
#' @importFrom S4Vectors metadata<-
runSeuratICA <-
  function(inSCE,
           useAssay = "seuratScaledData",
           useFeatureSubset = NULL,
           scale = TRUE,
           reducedDimName = "seuratICA",
           nics = 20,
           seed = 12345,
           verbose = FALSE) {
    params <- as.list(environment())
    params$inSCE <- NULL

    if (!isTRUE(scale)) {
      # If not doing a scaling, put useAssay as scaled as RunPCA need it
      seuratObject <-
        convertSCEToSeurat(inSCE, scaledAssay = useAssay)
    } else {
      # If doing scaling, put useAssay as normed, used by ScaleData first
      seuratObject <- convertSCEToSeurat(inSCE, normAssay = useAssay)
    }

    features <- .parseUseFeatureSubset(inSCE, useFeatureSubset,
                                       altExpObj = NULL,
                                       returnType = "character")
    if (is.null(features)) {
      if (length(Seurat::VariableFeatures(seuratObject)) == 0) {
        features <- rownames(inSCE)
      }
    }

    if (isTRUE(scale)) {
      seuratObject <-
        Seurat::ScaleData(seuratObject, features = features,
                          verbose = verbose)
    }

    seuratObject <- Seurat::RunICA(
      seuratObject,
      nics = as.double(nics),
      features = features,
      verbose = verbose,
      seed.use = seed
    )
    inSCE <- .addSeuratToMetaDataSCE(inSCE, seuratObject)

    temp <- seuratObject@reductions$ica@cell.embeddings
    rownames(temp) <- colnames(inSCE)
    reducedDim(inSCE, reducedDimName) <- temp
    metadata(inSCE)$sctk$runDimReduce$reddim[[reducedDimName]] <- params
    return(inSCE)
  }

#' runSeuratJackStraw
#' Compute jackstraw plot and store the computations in the input sce object
#' @param inSCE (sce) object on which to compute and store jackstraw plot
#' @param useAssay Specify name of the assay to use for scaling. Assay name
#'  provided against this parameter is scaled by the function and used
#'  for the computation of JackStraw scores along with the reduced dimensions
#'  specified by the \code{dims} parameter.
#' @param dims Number of components to test in Jackstraw. If \code{NULL}, then
#' all components are used. Default \code{NULL}.
#' @param numReplicate Numeric value indicating the number of replicate
#' samplings to perform.
#'  Default value is \code{100}.
#' @param propFreq Numeric value indicating the proportion of data to randomly
#' permute for each replicate.
#'  Default value is \code{0.025}.
#' @param externalReduction Pass DimReduc object if PCA/ICA computed through
#' other libraries. Default \code{NULL}.
#' @examples
#' data(scExample, package = "singleCellTK")
#' \dontrun{
#' sce <- runSeuratNormalizeData(sce, useAssay = "counts")
#' sce <- runSeuratFindHVG(sce, useAssay = "counts")
#' sce <- runSeuratScaleData(sce, useAssay = "counts")
#' sce <- runSeuratPCA(sce, useAssay = "counts")
#' sce <- runSeuratJackStraw(sce, useAssay = "counts")
#' }
#' @return Updated \code{SingleCellExperiment} object with jackstraw
#' computations stored in it
#' @export
runSeuratJackStraw <- function(inSCE,
                               useAssay,
                               dims = NULL,
                               numReplicate = 100,
                               propFreq = 0.025,
                               externalReduction = NULL) {
  seuratObject <- convertSCEToSeurat(inSCE, normAssay = useAssay)
  seuratObject <- Seurat::ScaleData(seuratObject)
  if (!is.null(externalReduction)) {
    #convert (_) to (-) as required by Seurat

    rownames(externalReduction@cell.embeddings) <-
      .convertToHyphen(rownames(externalReduction@cell.embeddings))
    seuratObject <- Seurat::FindVariableFeatures(seuratObject)
    seuratObject <- Seurat::ScaleData(seuratObject)
    seuratObject@reductions <- list(pca = externalReduction)
    seuratObject@reductions$pca@feature.loadings <-
      seuratObject@reductions$pca@feature.loadings[match(
        rownames(
          Seurat::GetAssayData(seuratObject, assay = "RNA", slot = "scale.data")
        ),
        rownames(seuratObject@reductions$pca@feature.loadings)
      ), ]
    if (any(is.na(seuratObject@reductions$pca@feature.loadings))) {
      seuratObject@reductions$pca@feature.loadings <-
        stats::na.omit(seuratObject@reductions$pca@feature.loadings)
    }
    seuratObject@commands$RunPCA.RNA <-
      seuratObject@commands$ScaleData.RNA
    seuratObject@commands$RunPCA.RNA@params$rev.pca <- FALSE
    seuratObject@commands$RunPCA.RNA@params$weight.by.var <- TRUE
  }
  if (is.null(seuratObject@reductions[["pca"]])) {
    stop("'runSeuratPCA' must be run before JackStraw can be computed.")
  }
  if (is.null(dims)) {
    dims <- ncol(seuratObject@reductions[["pca"]])
  }
  seuratObject <-
    Seurat::JackStraw(
      seuratObject,
      dims = as.double(dims),
      num.replicate = numReplicate,
      prop.freq = propFreq
    )
  seuratObject <-
    Seurat::ScoreJackStraw(seuratObject, dims = seq(dims))
  inSCE <- .addSeuratToMetaDataSCE(inSCE, seuratObject)
  return(inSCE)
}

#' plotSeuratJackStraw
#' Computes the plot object for jackstraw plot from the pca slot in the input
#' sce object
#' @param inSCE (sce) object from which to compute the jackstraw plot (pca
#' should be computed)
#' @param dims Number of components to plot in Jackstraw. If \code{NULL}, then
#' all components are plotted Default \code{NULL}.
#' @param xmax X-axis maximum on each QQ plot. Default \code{0.1}.
#' @param ymax Y-axis maximum on each QQ plot. Default \code{0.3}.
#' @param externalReduction Pass DimReduc object if PCA/ICA computed through
#' other libraries. Default \code{NULL}.
#' @examples
#' data(scExample, package = "singleCellTK")
#' \dontrun{
#' sce <- runSeuratNormalizeData(sce, useAssay = "counts")
#' sce <- runSeuratFindHVG(sce, useAssay = "counts")
#' sce <- runSeuratScaleData(sce, useAssay = "counts")
#' sce <- runSeuratPCA(sce, useAssay = "counts")
#' sce <- runSeuratJackStraw(sce, useAssay = "counts")
#' plotSeuratJackStraw(sce)
#' }
#' @return plot object
#' @export
plotSeuratJackStraw <-
  function(inSCE,
           dims = NULL,
           xmax = 0.1,
           ymax = 0.3,
           externalReduction = NULL) {
    seuratObject <- convertSCEToSeurat(inSCE)
    if (!is.null(externalReduction)) {
      seuratObject@reductions <- list(pca = externalReduction)
    }
    if (is.null(seuratObject@reductions[["pca"]])) {
      stop("'runSeuratPCA' must be run before JackStraw can be computed.")
    }
    if (is.null(dims)) {
      dims <- ncol(seuratObject@reductions[["pca"]])
    }
    return(Seurat::JackStrawPlot(
      seuratObject,
      dims = seq(dims),
      xmax = xmax,
      ymax = ymax
    ))
  }

#' plotSeuratHVG
#' Plot highly variable genes from input sce object (must have highly variable
#' genes computations stored)
#' @param inSCE (sce) object that contains the highly variable genes
#' computations
#' @param labelPoints Numeric value indicating the number of top genes that
#' should be labeled.
#'  Default is \code{0}, which will not label any point.
#' @examples
#' data(scExample, package = "singleCellTK")
#' \dontrun{
#' sce <- runSeuratNormalizeData(sce, useAssay = "counts")
#' sce <- runSeuratFindHVG(sce, useAssay = "counts")
#' plotSeuratHVG(sce)
#' }
#' @return plot object
#' @export
plotSeuratHVG <- function(inSCE, labelPoints = 0) {
  seuratObject <- convertSCEToSeurat(inSCE)
  plot <- Seurat::VariableFeaturePlot(seuratObject)
  plot$labels$colour <- "Variable"
  if (requireNamespace("stringr", quietly = TRUE)) {
    plot$data$colors <- stringr::str_to_title(plot$data$colors)
  }
  if (labelPoints > 0) {
    plot <- Seurat::LabelPoints(plot,
                                points = Seurat::VariableFeatures(object = seuratObject)[seq(labelPoints)])
  }
  return(plot)
}

#' plotSeuratReduction
#' Plots the selected dimensionality reduction method
#' @param inSCE (sce) object which has the selected dimensionality reduction
#' algorithm already computed and stored
#' @param useReduction Dimentionality reduction to plot. One of "pca", "ica",
#' "tsne", or "umap". Default \code{"umap"}.
#' @param showLegend Select if legends and labels should be shown on the output
#' plot or not. Either "TRUE" or "FALSE". Default \code{FALSE}.
#' @param groupBy Specify a colData column name that be used for grouping.
#' Default is \code{NULL}.
#' @param splitBy Specify a colData column name that be used for splitting the
#' output plot. Default is \code{NULL}.
#' @examples
#' data(scExample, package = "singleCellTK")
#' \dontrun{
#' sce <- runSeuratNormalizeData(sce, useAssay = "counts")
#' sce <- runSeuratFindHVG(sce, useAssay = "counts")
#' sce <- runSeuratScaleData(sce, useAssay = "counts")
#' sce <- runSeuratPCA(sce, useAssay = "counts")
#' plotSeuratReduction(sce, useReduction = "pca")
#' }
#' @return plot object
#' @export
plotSeuratReduction <-
  function(inSCE,
           useReduction = c("pca", "ica",
                            "tsne", "umap"),
           showLegend = FALSE,
           groupBy = NULL,
           splitBy = NULL) {
    seuratObject <- convertSCEToSeurat(inSCE)
    if (!is.null(seuratObject@meta.data$seurat_cluster)) {
      seuratObject@meta.data <-
        seuratObject@meta.data[, "seurat_clusters", drop = FALSE]
    }
    else{
      seuratObject@meta.data <- data.frame()
    }

    if (showLegend) {
      if (!is.null(seuratObject@meta.data$seurat_clusters)) {
        Seurat::Idents(seuratObject) <-
          seuratObject@meta.data$seurat_clusters
        #seuratObject@meta.data <- data.frame()
      }
    }

    if (!is.null(groupBy)) {
      seuratObject[[groupBy]] <- colData(inSCE)[[groupBy]]
    }

    if (!is.null(splitBy)) {
      seuratObject[[splitBy]] <- colData(inSCE)[[splitBy]]
    }

    if (showLegend) {
      plot <- Seurat::DimPlot(
        object = seuratObject,
        reduction = useReduction,
        group.by = groupBy,
        split.by = splitBy,
        label = TRUE
      )
    }
    else{
      plot <- Seurat::DimPlot(
        object = seuratObject,
        reduction = useReduction,
        group.by = groupBy,
        split.by = splitBy,
        label = FALSE
      ) + Seurat::NoLegend()
    }

    if ("ident" %in% names(plot$data) &&
        "seurat_clusters" %in% names(seuratObject@meta.data)) {
      plot$data$ident <- seuratObject@meta.data$seurat_clusters
    }
    return(plot)
  }


#' runSeuratFindClusters
#' Computes the clusters from the input sce object and stores them back in sce
#' object
#' @param inSCE (sce) object from which clusters should be computed and stored
#' in
#' @param useAssay Assay containing scaled counts to use for clustering.
#' @param useReduction Reduction method to use for computing clusters. One of
#' "pca" or "ica". Default \code{"pca"}.
#' @param dims numeric value of how many components to use for computing
#' clusters. Default \code{10}.
#' @param algorithm selected algorithm to compute clusters. One of "louvain",
#' "multilevel", or "SLM". Use \code{louvain} for "original Louvain algorithm"
#' and \code{multilevel} for "Louvain algorithm with multilevel refinement".
#' Default \code{louvain}.
#' @param groupSingletons boolean if singletons should be grouped together or
#' not. Default \code{TRUE}.
#' @param resolution Set the resolution parameter to find larger (value above 1)
#' or smaller (value below 1) number of communities. Default \code{0.8}.
#' @param seed Specify the seed value. Default \code{12345}.
#' @param externalReduction Pass DimReduc object if PCA/ICA computed through
#' other libraries. Default \code{NULL}.
#' @param verbose Logical value indicating if informative messages should
#'  be displayed. Default is \code{TRUE}.
#' @examples
#' data(scExample, package = "singleCellTK")
#' \dontrun{
#' sce <- runSeuratNormalizeData(sce, useAssay = "counts")
#' sce <- runSeuratFindHVG(sce, useAssay = "counts")
#' sce <- runSeuratScaleData(sce, useAssay = "counts")
#' sce <- runSeuratPCA(sce, useAssay = "counts")
#' sce <- runSeuratFindClusters(sce, useAssay = "counts")
#' }
#' @return Updated sce object which now contains the computed clusters
#' @export
runSeuratFindClusters <- function(inSCE,
                                  useAssay = "seuratNormData",
                                  useReduction = c("pca", "ica"),
                                  dims = 10,
                                  algorithm = c("louvain", "multilevel", "SLM"),
                                  groupSingletons = TRUE,
                                  resolution = 0.8,
                                  seed = 12345,
                                  externalReduction = NULL,
                                  verbose = TRUE) {
  algorithm <- match.arg(algorithm)
  useReduction <- match.arg(useReduction)

  if (!is.null(externalReduction)) {
    seuratObject <- convertSCEToSeurat(inSCE)
    seuratObject@reductions <- list(pca = externalReduction)
    useReduction <- "pca"
  } else {
    if (is.null(useReduction)) {
      seuratObject <- convertSCEToSeurat(inSCE, scaledAssay = useAssay)
    }
    else{
      seuratObject <- convertSCEToSeurat(inSCE)
    }
  }

  # Get version number of object
  seurat.version <- .getSeuratObjectMajorVersion(seuratObject)

  seuratObject <- withr::with_seed(seed, {
    Seurat::FindNeighbors(
      seuratObject,
      reduction = useReduction,
      dims = seq(dims),
      verbose = verbose
    )
  })

  no_algorithm <- 1
  if (algorithm == "louvain") {
    no_algorithm = 1
  } else if (algorithm == "multilevel") {
    no_algorithm = 2
  } else if (algorithm == "SLM") {
    no_algorithm = 3
  }
  tempSeuratObject <- seuratObject
  if(seurat.version < 5.0){
    tempSeuratObject@meta.data <- data.frame()
  }
  tempSeuratObject <- withr::with_seed(seed, {
    Seurat::FindClusters(
      tempSeuratObject,
      algorithm = no_algorithm,
      group.singletons = groupSingletons,
      resolution = resolution,
      verbose = verbose
    )
  })
  seuratObject@meta.data$seurat_clusters <-
    tempSeuratObject@meta.data$seurat_clusters
  inSCE <- .addSeuratToMetaDataSCE(inSCE, seuratObject)
  colData(inSCE)[[paste0("Seurat", "_", algorithm, "_", "Resolution", resolution)]] <-
    seuratObject@meta.data$seurat_clusters
  S4Vectors::metadata(inSCE)$seurat$clusterName <- paste0("Seurat", "_",
                                                         algorithm, "_",
                                                         "Resolution",
                                                         resolution)
  return(inSCE)
}

#' runSeuratTSNE
#' Computes tSNE from the given sce object and stores the tSNE computations back
#' into the sce object
#' @param inSCE (sce) object on which to compute the tSNE
#' @param useReduction selected reduction algorithm to use for computing tSNE.
#' One of "pca" or "ica". Default \code{"pca"}.
#' @param reducedDimName Name of new reducedDims object containing Seurat tSNE
#' Default \code{seuratTSNE}.
#' @param dims Number of reduction components to use for tSNE computation.
#' Default \code{10}.
#' @param perplexity Adjust the perplexity tuneable parameter for the underlying
#' tSNE call. Default \code{30}.
#' @param externalReduction Pass DimReduc object if PCA/ICA computed through
#' other libraries. Default \code{NULL}.
#' @param seed Random seed for reproducibility of results.
#' Default \code{1}.
#' @return Updated sce object with tSNE computations stored
#' @export
#' @importFrom SingleCellExperiment reducedDim<-
runSeuratTSNE <- function(inSCE,
                          useReduction = c("pca", "ica"),
                          reducedDimName = "seuratTSNE",
                          dims = 10,
                          perplexity = 30,
                          externalReduction = NULL,
                          seed = 1) {
  useReduction <- match.arg(useReduction)
  seuratObject <- convertSCEToSeurat(inSCE)

  if (!is.null(externalReduction)) {
    seuratObject@reductions <- list(pca = externalReduction)
    useReduction <- "pca"
  }

  seuratObject <-
    Seurat::RunTSNE(
      seuratObject,
      reduction = useReduction,
      dims = seq(dims),
      perplexity = perplexity,
      check_duplicates = FALSE,
      seed.use = seed
    )
  inSCE <- .addSeuratToMetaDataSCE(inSCE, seuratObject)
  temp <- seuratObject@reductions$tsne@cell.embeddings
  rownames(temp) <- colnames(inSCE)
  reducedDim(inSCE, reducedDimName) <- temp

  return(inSCE)
}

#' runSeuratUMAP
#' Computes UMAP from the given sce object and stores the UMAP computations back
#' into the sce object
#' @param inSCE (sce) object on which to compute the UMAP
#' @param useReduction Reduction to use for computing UMAP. One of "pca" or
#' "ica". Default is \code{"pca"}.
#' @param reducedDimName Name of new reducedDims object containing Seurat UMAP
#' Default \code{seuratUMAP}.
#' @param dims Numerical value of how many reduction components to use for UMAP
#' computation. Default \code{10}.
#' @param minDist Sets the \code{"min.dist"} parameter to the underlying UMAP
#' call. See \link[Seurat]{RunUMAP} for more information. Default \code{0.3}.
#' @param nNeighbors Sets the \code{"n.neighbors"} parameter to the underlying
#' UMAP call. See \link[Seurat]{RunUMAP} for more information. Default
#' \code{30L}.
#' @param spread Sets the \code{"spread"} parameter to the underlying UMAP call.
#' See \link[Seurat]{RunUMAP} for more information. Default \code{1}.
#' @param externalReduction Pass DimReduc object if PCA/ICA computed through
#' other libraries. Default \code{NULL}.
#' @param seed Random seed for reproducibility of results.
#' Default \code{42}.
#' @param verbose Logical value indicating if informative messages should
#'  be displayed. Default is \code{TRUE}.
#' @examples
#' data(scExample, package = "singleCellTK")
#' \dontrun{
#' sce <- runSeuratNormalizeData(sce, useAssay = "counts")
#' sce <- runSeuratFindHVG(sce, useAssay = "counts")
#' sce <- runSeuratScaleData(sce, useAssay = "counts")
#' sce <- runSeuratPCA(sce, useAssay = "counts")
#' sce <- runSeuratFindClusters(sce, useAssay = "counts")
#' sce <- runSeuratUMAP(sce, useReduction = "pca")
#' }
#' @return Updated sce object with UMAP computations stored
#' @export
#' @importFrom SingleCellExperiment reducedDim<-
runSeuratUMAP <- function(inSCE,
                          useReduction = c("pca", "ica"),
                          reducedDimName = "seuratUMAP",
                          dims = 10,
                          minDist = 0.3,
                          nNeighbors = 30L,
                          spread = 1,
                          externalReduction = NULL,
                          seed = 42,
                          verbose = TRUE) {
  useReduction <- match.arg(useReduction)
  seuratObject <- convertSCEToSeurat(inSCE)

  if (!is.null(externalReduction)) {
    seuratObject@reductions <- list(pca = externalReduction)
    useReduction <- "pca"
  }

  seuratObject <- Seurat::RunUMAP(
    seuratObject,
    reduction = useReduction,
    dims = seq(dims),
    min.dist = minDist,
    n.neighbors = nNeighbors,
    spread = spread,
    verbose = verbose,
    seed.use = seed
  )
  inSCE <- .addSeuratToMetaDataSCE(inSCE, seuratObject)

  temp <- seuratObject@reductions$umap@cell.embeddings
  rownames(temp) <- colnames(inSCE)
  reducedDim(inSCE, reducedDimName) <- temp

  return(inSCE)
}

#' .seuratGetVariableFeatures
#' Retrieves the requested number of variable feature names
#' @param inSCE (sce) object from which to extract the variable feature names
#' @param numberOfFeatures numerical value indicating how many feature names
#' should be retrieved (default is 100)
#' @return list() of variable feature names
#' @noRd
.seuratGetVariableFeatures <- function(inSCE, numberOfFeatures) {
  seuratObject <- convertSCEToSeurat(inSCE)

  # Get version number of object
  seurat.version <- .getSeuratObjectMajorVersion(seuratObject)

  if(seurat.version >= 5.0){
    if (length(SeuratObject::Features(seuratObject)) > 0) {
      return(SeuratObject::Features(seuratObject)[seq(numberOfFeatures)])
    }
    #if (length(seuratObject@assays$RNA@misc$var.features) > 0) {
    #  return(seuratObject@assays$RNA@misc$var.features[seq(numberOfFeatures)])
    #}
  }
  else{
    if (length(seuratObject@assays$RNA@meta.data$var.features) > 0) {
      return(seuratObject@assays$RNA@meta.data$var.features[seq(numberOfFeatures)])
    }
  }
}

#' plotSeuratElbow
#' Computes the plot object for elbow plot from the pca slot in the input sce
#' object
#' @param inSCE (sce) object from which to compute the elbow plot (pca should
#' be computed)
#' @param significantPC Number of significant principal components to plot.
#' This is used to alter the color of the points for the corresponding PCs.
#' If \code{NULL}, all points will be the same color. Default \code{NULL}.
#' @param reduction Reduction to use for elbow plot generation. Either
#' \code{"pca"} or \code{"ica"}. Default \code{"pca"}.
#' @param ndims Number of components to use. Default \code{20}.
#' @param externalReduction Pass DimReduc object if PCA/ICA computed through
#' other libraries. Default \code{NULL}.
#' @param interactive Logical value indicating if the returned object should
#'  be an interactive plotly object if \code{TRUE} or a ggplot object if
#'  set to \code{FALSE}. Default is \code{FALSE}.
#' @examples
#' data(scExample, package = "singleCellTK")
#' \dontrun{
#' sce <- runSeuratNormalizeData(sce, useAssay = "counts")
#' sce <- runSeuratFindHVG(sce, useAssay = "counts")
#' sce <- runSeuratScaleData(sce, useAssay = "counts")
#' sce <- runSeuratPCA(sce, useAssay = "counts")
#' plotSeuratElbow(sce)
#' }
#' @return plot object
#' @export
plotSeuratElbow <- function(inSCE,
                            significantPC = NULL,
                            reduction = "pca",
                            ndims = 20,
                            externalReduction = NULL,
                            interactive = FALSE) {
  seuratObject <- convertSCEToSeurat(inSCE)
  if (!is.null(externalReduction)) {
    seuratObject@reductions <- list(pca = externalReduction)
    reduction <- "pca"
  }
  plot <-
    Seurat::ElbowPlot(seuratObject, reduction = reduction, ndims = ndims)
  if (!is.null(significantPC)) {
    if (significantPC > ndims) {
      significantPC <- ndims
    }
    plot$data$Significant <- c(rep("Yes", significantPC),
                               rep("No", length(rownames(plot$data)) - significantPC))
    plot <- ggplot2::ggplot(
      data = plot$data,
      ggplot2::aes(
        x = plot$data$dims,
        y = plot$data$stdev,
        color = plot$data$Significant
      )
    ) +
      ggplot2::geom_point()
  }
  plot$labels$x <- "PC"
  plot$labels$y <- "Standard Deviation"
  plot$labels$colour <- "Significant"

  if (interactive) {
    hoverText <-
      paste(
        "Dimension:",
        plot$data$dims,
        "\nStandard Deviation:",
        round(plot$data$stdev, 1),
        "\nIs Significant?",
        plot$data$Significant
      )
    significant <- plot$data$Significant
    if (length(unique(significant)) > 1) {
      plot <-
        plotly::style(plot, text = hoverText[seq(which(significant == "No")[1]) -
                                               1])
      plot <-
        plotly::style(plot, text = hoverText[which(significant == "No")[1]:length(significant)], traces = 1)
    }
    else{
      plot <- plotly::style(plot, text = hoverText)
    }
  }

  return(plot)
}

#' runSeuratHeatmap
#' Computes the heatmap plot object from the pca slot in the input sce object
#' @param inSCE (sce) object from which to compute heatmap (pca should be
#' computed)
#' @param useAssay Specify name of the assay that will be scaled
#'  by this function. The output scaled assay will be used for computation
#'  of the heatmap.
#' @param useReduction Reduction method to use for computing clusters. One of
#' "pca" or "ica". Default \code{"pca"}.
#' @param dims Number of components to generate heatmap plot objects. If
#' \code{NULL}, a heatmap will be generated for all components. Default
#' \code{NULL}.
#' @param nfeatures Number of features to include in the heatmap. Default
#' \code{30}.
#' @param cells Numeric value indicating the number of top cells to plot.
#'  Default is \code{NULL} which indicates all cells.
#' @param ncol Numeric value indicating the number of columns to use for plot.
#'  Default is \code{NULL} which will automatically compute accordingly.
#' @param balanced Plot equal number of genes with positive and negative scores.
#'  Default is \code{TRUE}.
#' @param fast See \link[Seurat]{DimHeatmap} for more information. Default
#' \code{TRUE}.
#' @param combine See \link[Seurat]{DimHeatmap} for more information. Default
#' \code{TRUE}.
#' @param raster See \link[Seurat]{DimHeatmap} for more information. Default
#' \code{TRUE}.
#' @param externalReduction Pass DimReduc object if PCA/ICA computed through
#' other libraries. Default \code{NULL}.
#' @examples
#' data(scExample, package = "singleCellTK")
#' \dontrun{
#' sce <- runSeuratNormalizeData(sce, useAssay = "counts")
#' sce <- runSeuratFindHVG(sce, useAssay = "counts")
#' sce <- runSeuratScaleData(sce, useAssay = "counts")
#' sce <- runSeuratPCA(sce, useAssay = "counts")
#' heatmap <- runSeuratHeatmap(sce, useAssay = "counts")
#' plotSeuratHeatmap(heatmap)
#' }
#' @return plot object
#' @export
runSeuratHeatmap <- function(inSCE,
                             useAssay,
                             useReduction = c("pca", "ica"),
                             dims = NULL,
                             nfeatures = 30,
                             cells = NULL,
                             ncol = NULL,
                             balanced = TRUE,
                             fast = TRUE,
                             combine = TRUE,
                             raster = TRUE,
                             externalReduction = NULL) {
  useReduction <- match.arg(useReduction)
  # seuratObject <- convertSCEToSeurat(inSCE, scaledAssay = useAssay)
  seuratObject <- convertSCEToSeurat(inSCE, normAssay = useAssay)

  if (!is.null(externalReduction)) {
    seuratObject@reductions <- list(pca = externalReduction)
    useReduction <- "pca"
  }
  if (is.null(dims)) {
    dims <- ncol(seuratObject@reductions[[useReduction]])
  }

  # Only scale requested features
  featuresToScale <- NULL
  temp <-
    as.data.frame(seuratObject[[useReduction]]@feature.loadings)
  for (i in seq(dims)) {
    featuresToScale <-
      c(featuresToScale, rownames(temp[order(-temp[[i]]),])[seq(nfeatures)])
    featuresToScale <-
      c(featuresToScale, rownames(temp[order(temp[[i]]),])[seq(nfeatures)])
  }
  seuratObject <-
    Seurat::ScaleData(seuratObject, features = featuresToScale)

  plotObject <- Seurat::DimHeatmap(
    seuratObject,
    dims = seq(dims),
    nfeatures = nfeatures,
    cells = cells,
    reduction = useReduction,
    ncol = ncol,
    fast = fast,
    combine = combine,
    raster = raster,
    balanced = balanced
  )
  return(plotObject)
}

#' plotSeuratHeatmap
#' Modifies the heatmap plot object so it contains specified number of heatmaps
#' in a single plot
#' @param plotObject plot object computed from runSeuratHeatmap() function
#' @param dims numerical value of how many heatmaps to draw (default is 0)
#' @param ncol numerical value indicating that in how many columns should the
#' heatmaps be distrbuted (default is 2)
#' @param labels list() of labels to draw on heatmaps
#' @return modified plot object
#' @export
plotSeuratHeatmap <- function(plotObject, dims, ncol, labels) {
  componentsToPlot <- as.integer(gsub("[^0-9.]", "", labels))
  return(cowplot::plot_grid(
    plotlist = plotObject[c(componentsToPlot)],
    ncol = ncol,
    labels = labels
  ))
}

#' .updateAssaySCE
#' Update/Modify/Add an assay in the provided SingleCellExperiment object from
#' a Seurat object
#' @param inSCE Input SingleCellExperiment object
#' @param seuratObject Input Seurat object
#' @param assaySlotSCE Selected assay to update in the input
#' SingleCellExperiment object
#' @param seuratDataSlot Selected data slot from the Seurat object. Default
#' \code{"counts"}.
#' @param seuratAssaySlot Selected assay from Seurat object. Default
#' \code{"RNA"}.
#' @return A \link[SingleCellExperiment]{SingleCellExperiment} object with
#'  data from Seurat object appended to the \link{assay} slot.
#' @importFrom SummarizedExperiment assay<-
#' @noRd
.updateAssaySCE <- function(inSCE,
                            seuratObject,
                            assaySlotSCE,
                            seuratDataSlot = "counts",
                            seuratAssaySlot = "RNA") {
  assay(inSCE, assaySlotSCE) <- NULL

  # Get version number of object
  seurat.version <- .getSeuratObjectMajorVersion(seuratObject)

  if(seurat.version >= 5.0){
    temp.matrix <- seuratObject[[seuratAssaySlot]][seuratDataSlot]
  }
  else{
    temp.matrix <-
      methods::slot(Seurat::GetAssay(seuratObject, seuratAssaySlot),
                    seuratDataSlot)
  }
  colnames(temp.matrix) <- colnames(inSCE)

  if (seuratDataSlot == "scale.data" || assaySlotSCE == "SCTCounts") {
    altExp(inSCE, assaySlotSCE) <-
      SingleCellExperiment(list(counts = temp.matrix))
  }
  else{
    rownames(temp.matrix) <- rownames(inSCE)
    assay(inSCE, assaySlotSCE) <- temp.matrix
  }
  return(inSCE)
}

#' convertSeuratToSCE
#' Converts the input seurat object to a sce object
#' @param seuratObject Input Seurat object
#' @param normAssayName Name of assay to store the normalized data. Default
#' \code{"seuratNormData"}.
#' @param scaledAssayName Name of assay to store the scaled data. Default
#' \code{"seuratScaledData"}.
#' @examples
#' data(scExample, package = "singleCellTK")
#' seurat <- convertSCEToSeurat(sce)
#' sce <- convertSeuratToSCE(seurat)
#' @return \code{SingleCellExperiment} output object
#' @export
convertSeuratToSCE <-
  function(seuratObject,
           normAssayName = "seuratNormData",
           scaledAssayName = "seuratScaledData") {

    # Get version number of object
    seurat.version <- .getSeuratObjectMajorVersion(seuratObject)

    if (seurat.version >= 5.0){
      inSCE <- SingleCellExperiment(
        assays = list(counts = seuratObject@assays[[1]]$counts),
        colData = seuratObject@meta.data
      )
      assay(inSCE, normAssayName) <-
        seuratObject[["RNA"]]["data"]
      if (length(seuratObject[["RNA"]]["scale.data"]) > 0) {
        altExp(inSCE, scaledAssayName) <- SingleCellExperiment::SingleCellExperiment(
          list(counts = seuratObject[["RNA"]]["scale.data"]))
      }
    }
    else{
      inSCE <- SingleCellExperiment(
        assays = list(counts = seuratObject@assays[[1]]@counts),
        colData = seuratObject@meta.data
      )
      assay(inSCE, normAssayName) <-
        methods::slot(seuratObject@assays$RNA, "data")
      if (length(methods::slot(seuratObject, "assays")[["RNA"]]@scale.data) > 0) {
        altExp(inSCE, scaledAssayName) <- SingleCellExperiment::SingleCellExperiment(
          list(counts = methods::slot(seuratObject@assays$RNA, "scale.data")))
      }
    }

    inSCE <- .addSeuratToMetaDataSCE(inSCE, seuratObject)
    return(inSCE)
  }

#' convertSCEToSeurat
#' Converts sce object to seurat while retaining all assays and metadata
#' @param inSCE A \code{SingleCellExperiment} object to convert to a Seurat
#'  object.
#' @param countsAssay Which assay to use from sce object for raw counts.
#'  Default \code{NULL}.
#' @param normAssay Which assay to use from sce object for normalized data.
#'  Default \code{NULL}.
#' @param scaledAssay Which assay to use from sce object for scaled data.
#'  Default \code{NULL}.
#' @param copyColData Boolean. Whether copy 'colData' of SCE object to
#'  the 'meta.data' of Seurat object. Default \code{FALSE}.
#' @param copyReducedDim Boolean. Whether copy 'reducedDims' of the SCE
#'  object to the 'reductions' of Seurat object. Default \code{FALSE}.
#' @param copyDecontX Boolean. Whether copy 'decontXcounts' assay of the
#'  SCE object to the 'assays' of Seurat object. Default \code{TRUE}.
#' @param pcaReducedDim Specify a character value indicating the name of
#'  the reducedDim to store as default pca computation in the output seurat
#'  object. Default is \code{NULL} which will not store any reducedDim as the
#'  default pca. This will only work when \code{copyReducedDim} parameter is
#'  set to \code{TRUE}.
#' @param icaReducedDim Specify a character value indicating the name of
#'  the reducedDim to store as default ica computation in the output seurat
#'  object. Default is \code{NULL} which will not store any reducedDim as the
#'  default ica. This will only work when \code{copyReducedDim} parameter is
#'  set to \code{TRUE}.
#' @param tsneReducedDim Specify a character value indicating the name of
#'  the reducedDim to store as default tsne computation in the output seurat
#'  object. Default is \code{NULL} which will not store any reducedDim as the
#'  default tsne. This will only work when \code{copyReducedDim} parameter is
#'  set to \code{TRUE}.
#' @param umapReducedDim Specify a character value indicating the name of
#'  the reducedDim to store as default umap computation in the output seurat
#'  object. Default is \code{NULL} which will not store any reducedDim as the
#'  default umap. This will only work when \code{copyReducedDim} parameter is
#'  set to \code{TRUE}.
#' @examples
#' data(scExample, package = "singleCellTK")
#' seurat <- convertSCEToSeurat(sce)
#' @return Updated seurat object that contains all data from the input sce
#' object
#' @export
#' @importFrom SummarizedExperiment assay assays
#' @importFrom utils packageVersion
convertSCEToSeurat <-
  function(inSCE,
           countsAssay = NULL,
           normAssay = NULL,
           scaledAssay = NULL,
           copyColData = FALSE,
           copyReducedDim = FALSE,
           copyDecontX = FALSE,
           pcaReducedDim = NULL,
           icaReducedDim = NULL,
           tsneReducedDim = NULL,
           umapReducedDim = NULL) {
    .checkSCEValidity(inSCE)

    if (!is.null(countsAssay) &&
        !(countsAssay %in% expDataNames(inSCE))) {
      stop(paste0(
        "'",
        countsAssay,
        "' not found in the list of assays: ",
        paste(names(assays(inSCE)), collapse = ",")
      ))
    }
    if (!is.null(normAssay) &&
        !(normAssay %in% expDataNames(inSCE))) {
      stop(paste0(
        "'",
        normAssay,
        "' not found in the list of assays: ",
        paste(names(assays(inSCE)), collapse = ",")
      ))
    }
    if (!is.null(scaledAssay) &&
        !(scaledAssay %in% expDataNames(inSCE))) {
      stop(paste0(
        "'",
        scaledAssay,
        "' not found in the list of assays: ",
        paste(names(assays(inSCE)), collapse = ",")
      ))
    }
    if (!is.null(pcaReducedDim) &&
        !(pcaReducedDim %in% reducedDimNames(inSCE))) {
      stop(paste0(
        "'",
        pcaReducedDim,
        "' not found in the list of reducedDims: ",
        paste(reducedDimNames(inSCE), collapse = ",")
      ))
    }
    if (!is.null(icaReducedDim) &&
        !(icaReducedDim %in% reducedDimNames(inSCE))) {
      stop(paste0(
        "'",
        icaReducedDim,
        "' not found in the list of reducedDims: ",
        paste(reducedDimNames(inSCE), collapse = ",")
      ))
    }
    if (!is.null(tsneReducedDim) &&
        !(tsneReducedDim %in% reducedDimNames(inSCE))) {
      stop(paste0(
        "'",
        tsneReducedDim,
        "' not found in the list of reducedDims: ",
        paste(reducedDimNames(inSCE), collapse = ",")
      ))
    }
    if (!is.null(umapReducedDim) &&
        !(umapReducedDim %in% reducedDimNames(inSCE))) {
      stop(paste0(
        "'",
        umapReducedDim,
        "' not found in the list of reducedDims: ",
        paste(reducedDimNames(inSCE), collapse = ",")
      ))
    }

    # Seurat has a particular way of modifying row/colnames
    # Save row/colnames in metadata
    seuratRowNames <- gsub("_", "-", rownames(inSCE))
    seuratColNames <- gsub("_", "-", colnames(inSCE))
    inSCE@metadata$seurat$colNames <- seuratColNames
    inSCE@metadata$seurat$rowNames <- seuratRowNames

    # Create Seurat object and Set counts assay
    # If no counts assay is supplied, the first assay is used
    if (!is.null(countsAssay) &&
        countsAssay %in% names(assays(inSCE))) {
      temp <- .convertToMatrix(assay(inSCE, countsAssay))
    } else {
      temp <- .convertToMatrix(assays(inSCE)[[1]])
    }
    rownames(temp) <- seuratRowNames
    colnames(temp) <- seuratColNames
    seuratObject <- Seurat::CreateSeuratObject(counts = temp)
    seurat.version <- .getSeuratObjectMajorVersion(seuratObject)

    # Set normalized assay
    if (!is.null(normAssay) && normAssay %in% names(assays(inSCE))) {
      tempMatrix <- .convertToMatrix(assay(inSCE, normAssay))
      if (inherits(tempMatrix, "dgeMatrix")) {
        tempMatrix <- methods::as(tempMatrix, "CsparseMatrix")
      }
      rownames(tempMatrix) <- seuratRowNames
      colnames(tempMatrix) <- seuratColNames
      if(seurat.version >= 5.0){
        seuratObject[["RNA"]]$data  <- tempMatrix
      }
      else{
        seuratObject@assays$RNA@data <- tempMatrix
      }
    }else {
      if(seurat.version >= 5.0){
        tempMatrix <- Seurat::NormalizeData(seuratObject[["RNA"]]$counts, verbose = FALSE)
        rownames(tempMatrix) <- seuratRowNames
        colnames(tempMatrix) <- seuratColNames
        seuratObject[["RNA"]]$data  <- tempMatrix
      }
    }

    # Set Scaled Assay
    if (!is.null(scaledAssay) &&
        scaledAssay %in% names(assays(inSCE))) {
      tempMatrix <- as.matrix(assay(inSCE, scaledAssay))
      rownames(tempMatrix) <- seuratRowNames
      colnames(tempMatrix) <- seuratColNames

      if(seurat.version >= 5.0){
        seuratObject[["RNA"]]$scale.data  <- tempMatrix
      }
      else{
        seuratObject@assays$RNA@scale.data <- tempMatrix
      }
    }

    if(seurat.version >= 5.0){
      if (!is.null(inSCE@metadata$seurat$obj)) {
        # what is it looking for here? sequence? idk

        if ((nrow(inSCE@metadata$seurat$obj@assays$RNA) > 0 && ncol(inSCE@metadata$seurat$obj@assays$RNA) > 0) && !is.null(inSCE@metadata$seurat$obj@assays$RNA@meta.data$var.features)) {
          seuratObject@assays$RNA@meta.data$var.features <-
            inSCE@metadata$seurat$obj@assays$RNA@meta.data$var.features
        }

        # if no, then set a new matrix to empty
        # if (is.null(inSCE@metadata$seurat$obj@assays$RNA@meta.data$var.features)) {
        #   seuratObject@assays$RNA@meta.data$var.features <-
        #     data.frame(matrix(NA, nrow = nrow(inSCE@metadata$seurat$obj@assays$RNA), ncol = ncol(inSCE@metadata$seurat$obj@assays$RNA)))
        # }

        if (!is.null(inSCE@metadata$seurat$obj@reductions) && !is.null(inSCE@metadata$seurat$obj@reductions$pca)) {
          seuratObject@reductions$pca <-
            inSCE@metadata$seurat$obj@reductions$pca
        }
        if ((nrow(inSCE@metadata$seurat$obj@assays$RNA) > 0 && ncol(inSCE@metadata$seurat$obj@assays$RNA) > 0) && !is.null(inSCE@metadata$seurat$obj@assays$RNA@meta.data)) {
          seuratObject@assays$RNA@meta.data <-
            inSCE@metadata$seurat$obj@assays$RNA@meta.data
          #seuratObject <-
          #  SeuratObject::AddMetaData(seuratObject, inSCE@metadata$seurat$obj@assays$RNA@meta.data)
        }
        if (!is.null(inSCE@metadata$seurat$obj@reductions$ica)) {
          seuratObject@reductions$ica <-
            inSCE@metadata$seurat$obj@reductions$ica
        }
        if (!is.null(inSCE@metadata$seurat$obj@reductions$tsne)) {
          seuratObject@reductions$tsne <-
            inSCE@metadata$seurat$obj@reductions$tsne
        }
        if (!is.null(inSCE@metadata$seurat$obj@reductions$umap)) {
          seuratObject@reductions$umap <-
            inSCE@metadata$seurat$obj@reductions$umap
        }
        if (!is.null(inSCE@metadata$seurat$obj@meta.data)) {
          #seuratObject@meta.data <-
            #inSCE@metadata$seurat$obj$meta.data[match(colnames(seuratObject), rownames(inSCE@metadata$seurat$obj$meta.data)),]
          seuratObject <-
            SeuratObject::AddMetaData(seuratObject, inSCE@metadata$seurat$obj@meta.data[match(colnames(seuratObject), rownames(inSCE@metadata$seurat$obj@meta.data)),])
        }
        if (!is.null(inSCE@metadata$seurat$obj@commands)) {
          seuratObject@commands <- inSCE@metadata$seurat$obj@commands
        }
      }
    }
    else{
      if (!is.null(inSCE@metadata$seurat$obj)) {
        if (length(inSCE@metadata$seurat$obj@assays$RNA@meta.data$var.features) > 0) {
          seuratObject@assays$RNA@meta.data$var.features <-
            inSCE@metadata$seurat$obj@assays$RNA@meta.data$var.features
        }
        if (!is.null(inSCE@metadata$seurat$obj@reductions$pca)) {
          seuratObject@reductions$pca <-
            inSCE@metadata$seurat$obj@reductions$pca
        }
        if (!is.null(inSCE@metadata$seurat$obj@assays$RNA@meta.features)) {
          seuratObject@assays$RNA@meta.features <-
            inSCE@metadata$seurat$obj@assays$RNA@meta.features
        }
        if (!is.null(inSCE@metadata$seurat$obj@reductions$ica)) {
          seuratObject@reductions$ica <-
            inSCE@metadata$seurat$obj@reductions$ica
        }
        if (!is.null(inSCE@metadata$seurat$obj@reductions$tsne)) {
          seuratObject@reductions$tsne <-
            inSCE@metadata$seurat$obj@reductions$tsne
        }
        if (!is.null(inSCE@metadata$seurat$obj@reductions$umap)) {
          seuratObject@reductions$umap <-
            inSCE@metadata$seurat$obj@reductions$umap
        }
        if (!is.null(inSCE@metadata$seurat$obj@meta.data)) {
          #seuratObject@meta.data <- inSCE@metadata$seurat$obj@meta.data[match(colnames(seuratObject), rownames(inSCE@metadata$seurat$obj@meta.data)),]
          seuratObject <-
            SeuratObject::AddMetaData(seuratObject, inSCE@metadata$seurat$obj$meta.data[match(colnames(seuratObject), rownames(inSCE@metadata$seurat$obj$meta.data)),])
        }
        if (!is.null(inSCE@metadata$seurat$obj@commands)) {
          seuratObject@commands <- inSCE@metadata$seurat$obj@commands
        }
      }
    }
    # Set colData from inSCE object if required
    if (!is.null(colData(inSCE)) && copyColData) {
      seuratObject@meta.data <-
        cbind(seuratObject@meta.data, colData(inSCE))
      #seuratObject <- SeuratObject::AddMetaData(seuratObject, colData(inSCE))
    }

    # Set additional reducedDims from inSCE object if required
    if (length(SingleCellExperiment::reducedDims(inSCE)) > 0 &&
        copyReducedDim) {
      for (redc in SingleCellExperiment::reducedDimNames(inSCE)) {
        reDim <- SingleCellExperiment::reducedDim(inSCE, redc)
        colnames(reDim) <-
          paste0(redc, "_", seq_len(ncol(reDim))) #length(colnames(reDim)))
        rownames(reDim) <- gsub('_', '-', rownames(reDim))
        key <-  gsub('_', '', redc)
        seuratObject@reductions[[redc]] <-
          Seurat::CreateDimReducObject(embeddings = reDim,
                                       key = paste0(key, "_"),
                                       assay = "RNA")
      }

      availReducedDims <- c("pca", "ica", "tsne", "umap")
      for (i in availReducedDims) {
        if (!is.null(eval(parse(text = paste0(
          i, "ReducedDim"
        ))))) {
          seuratObject@reductions[[i]] <-
            seuratObject@reductions[[eval(parse(text = paste0(i, "ReducedDim")))]]
          seuratObject@reductions[[eval(parse(text = paste0(i, "ReducedDim")))]] <-
            NULL
          p <- paste0(
            i, "ReducedDim"
          )
          message(
            "'",
            eval(parse(text = p)),
            "' reducedDim from input SCE object saved to the default ",
            i,
            " slot of seurat object."
          )
        }
      }
    }

    # Set row data
    if(seurat.version >= 5) {
        seuratObject@assays$RNA@meta.data <- as.data.frame(rowData(inSCE))
    } else {
        seuratObject@assays$RNA@meta.features <- as.data.frame(rowData(inSCE))
    }

    # Set 'decontXCounts' assay to seurat object if required
    if ("decontXcounts" %in% SummarizedExperiment::assayNames(inSCE) &&
        copyDecontX) {
      decontM <- SummarizedExperiment::assay(inSCE, "decontXcounts")
      colnames(decontM) <- colnames(seuratObject)
      rownames(decontM) <- gsub('_', '-', rownames(decontM))
      if(seurat.version >= 5.0){
        # CreateAssayObject and similar were moved to SeuratObject now (added it to suggests)
        seuratObject[["decontXcounts"]] <-
          SeuratObject::CreateAssay5Object(counts = .convertToMatrix(decontM))
      }
      else{
        seuratObject[["decontXcounts"]] <-
          SeuratObject::CreateAssayObject(counts = .convertToMatrix(decontM))
      }

    }

    # Ensuring that colnames from input SCE converted to Seurat object are same in the Seurat metadata slot
    seuratObject@meta.data <- seuratObject@meta.data[colnames(seuratObject),]
    rownames(seuratObject@meta.data) <- colnames(seuratObject)

    return(seuratObject)
  }

#' runSeuratSCTransform
#' Runs the \link[Seurat]{SCTransform} function to transform/normalize the input
#' data
#' @param inSCE Input SingleCellExperiment object
#' @param normAssayName Name for the output data assay. Default
#' \code{"SCTCounts"}.
#' @param useAssay Name for the input data assay. Default \code{"counts"}.
#' @param verbose Logical value indicating if informative messages should
#'  be displayed. Default is \code{TRUE}.
#' @return Updated SingleCellExperiment object containing the transformed data
#' @export
#' @examples
#' data("mouseBrainSubsetSCE", package = "singleCellTK")
#' mouseBrainSubsetSCE <- runSeuratSCTransform(mouseBrainSubsetSCE)
runSeuratSCTransform <- function(inSCE,
                                 normAssayName = "SCTCounts",
                                 useAssay = "counts",
                                 verbose = TRUE) {
  seuratObject <- base::suppressWarnings(
    Seurat::SCTransform(
      object = convertSCEToSeurat(inSCE, useAssay),
      assay = "RNA",
      new.assay.name = "SCTransform",
      do.correct.umi = FALSE,
      verbose = verbose
    )
  )
  inSCE <-
    .updateAssaySCE(
      inSCE = inSCE,
      seuratObject = seuratObject,
      assaySlotSCE = normAssayName,
      seuratDataSlot = "data",
      seuratAssaySlot = "SCTransform"
    )
  inSCE <- expSetDataTag(inSCE = inSCE,
                         assayType = "normalized",
                         assays = normAssayName)
  return(inSCE)
}


#' .seuratInvalidate
#' Removes seurat data from the input SingleCellExperiment object specified by
#' the task in the Seurat workflow.
#' @param inSCE Input \code{SingleCellExperiment} object to remove Seurat data
#' from.
#' @param scaleData Remove scaled data from seurat. Default \code{TRUE}.
#' @param varFeatures Remove variable features from seurat. Default \code{TRUE}.
#' @param PCA Remove PCA from seurat. Default \code{TRUE}.
#' @param ICA Remove ICA from seurat. Default \code{TRUE}.
#' @param tSNE Remove tSNE from seurat. Default \code{TRUE}.
#' @param UMAP Remove UMAP from seurat. Default \code{TRUE}.
#' @param clusters Remove clusters from seurat. Default \code{TRUE}.
#' @return Updated SingleCellExperiment object containing the Seurat object in
#' the metadata slot with the data removed
#' @importFrom SummarizedExperiment assay<-
#' @noRd
.seuratInvalidate <-
  function(inSCE,
           scaleData = TRUE,
           varFeatures = TRUE,
           PCA = TRUE,
           ICA = TRUE,
           tSNE = TRUE,
           UMAP = TRUE,
           clusters = TRUE) {

    if (scaleData) {
      altExp(inSCE, "seuratScaledData") <- NULL
    }

    if(methods::is(inSCE@metadata$seurat$obj, "list")){
      if (varFeatures) {
        inSCE@metadata$seurat$obj$RNA$"var.features" <- NULL
        inSCE@metadata$seurat$obj$RNA$meta.features <-
          data.frame(row.names = make.unique(gsub("_", "-", rownames(inSCE))))
        #inSCE@metadata$seurat$heatmap_pca <- NULL
      }
      if (PCA) {
        inSCE@metadata$seurat$obj@reductions$pca <- NULL
      }
      if (ICA) {
        inSCE@metadata$seurat$obj@reductions$ica <- NULL
      }
      if (tSNE) {
        inSCE@metadata$seurat$obj@reductions$tsne <- NULL
      }
      if (UMAP) {
        inSCE@metadata$seurat$obj@reductions$umap <- NULL
      }
      if (clusters) {
        inSCE@metadata$seurat$obj$meta.data$seurat_clusters <- NULL
      }
    }
    if(methods::is(inSCE@metadata$seurat$obj, "Seurat")) {
      if (varFeatures) {
        seurat.version <- .getSeuratObjectMajorVersion(inSCE@metadata$seurat$obj)
        if (seurat.version >= 5.0) {
          #methods::slot(inSCE@metadata$seurat$obj, "assays")[["RNA"]]@meta.data$var.features <-
          #  logical()
          methods::slot(inSCE@metadata$seurat$obj, "assays")[["RNA"]]@meta.data <-
          data.frame(row.names = make.unique(gsub("_", "-", rownames(inSCE))))
        }
        else {
          methods::slot(inSCE@metadata$seurat$obj, "assays")[["RNA"]]@var.features <-
            logical()
          methods::slot(inSCE@metadata$seurat$obj, "assays")[["RNA"]]@meta.features <-
            data.frame(row.names = make.unique(gsub("_", "-", rownames(inSCE))))
        }
        inSCE@metadata$seurat$heatmap_pca <- NULL
      }
      if (PCA) {
        inSCE@metadata$seurat$obj@reductions$pca <- NULL
      }
      if (ICA) {
        inSCE@metadata$seurat$obj@reductions$ica <- NULL
      }
      if (tSNE) {
        inSCE@metadata$seurat$obj@reductions$tsne <- NULL
      }
      if (UMAP) {
        inSCE@metadata$seurat$obj@reductions$umap <- NULL
      }
      if (clusters) {
        inSCE@metadata$seurat$obj@meta.data$seurat_clusters <- NULL
      }

    }

    return(inSCE)
  }


#' runSeuratIntegration
#' A wrapper function to Seurat Batch-Correction/Integration workflow.
#' @param inSCE Input \code{SingleCellExperiment} object that contains the assay
#' to batch-correct.
#' @param useAssay Assay to batch-correct.
#' @param batch Batch variable from \code{colData} slot of
#' \code{SingleCellExperiment} object.
#' @param newAssayName Assay name for the batch-corrected output assay.
#' @param kAnchor Number of neighbours to use for finding the anchors in the
#' \link[Seurat]{FindIntegrationAnchors} function.
#' @param kFilter Number of neighbours to use for filtering the anchors in the
#' \link[Seurat]{FindIntegrationAnchors} function.
#' @param kWeight Number of neighbours to use when weigthing the anchors in the
#' \link[Seurat]{IntegrateData} function.
#' @param ndims Number of dimensions to use. Default \code{10}.
#'
#' @return A \code{SingleCellExperiment} object that contains the
#' batch-corrected assay inside the \code{altExp} slot of the object
#' @export
runSeuratIntegration <- function(inSCE,
                                 useAssay = "counts",
                                 batch,
                                 newAssayName = "SeuratIntegratedAssay",
                                 kAnchor,
                                 kFilter,
                                 kWeight,
                                 ndims = 10) {
  if (!useAssay %in% SummarizedExperiment::assayNames(inSCE)) {
    p <- paste(useAssay, "not found in the input object assays")
    stop(p)
  }
  if (is.null(batch)) {
    stop("batch variable must be provided for batch-correction")
  }
  if (kAnchor == 0 || kFilter == 0 || kWeight == 0) {
    stop("kAnchor, kFilter or kWeight cannot be zero. Please input correct ",
         "parameters.")
  }

  #create seurat object
  seuratObject <- convertSCEToSeurat(inSCE, useAssay)
  rownames(seuratObject@meta.data) <- gsub("_", "-",
                                           rownames(seuratObject@meta.data))
  seuratObject@meta.data <-
    cbind(seuratObject@meta.data, colData(inSCE))

  #split seurat object by batch variable
  seurat.list <- Seurat::SplitObject(seuratObject, split.by = batch)
  seurat.list <-
    seurat.list[c(unique(seuratObject@meta.data[[batch]]))]

  #find anchors
  seurat.anchors <-
    Seurat::FindIntegrationAnchors(
      object.list = seurat.list,
      dims = seq(ndims),
      k.anchor = kAnchor,
      k.filter = kFilter
    )
  seurat.integrated <-
    Seurat::IntegrateData(anchorset = seurat.anchors,
                          dims = seq(ndims),
                          k.weight = kWeight)
  #store results back in altExp slot of sce object
  altExp(inSCE, newAssayName) <-
    SingleCellExperiment(list(
      counts = Seurat::GetAssayData(seurat.integrated@assays$integrated, "data")
    ))
  SummarizedExperiment::assayNames(altExp(inSCE, newAssayName)) <-
    newAssayName
  # remove this if counts in above line set to altExp

  # store back colData from sce into the altExp slot
  colData(altExp(inSCE, newAssayName)) <- colData(inSCE)

  #counts <- assay(altExp(inSCE, newAssayName), "altExp")
  # remove NA values from counts and replace with zero so can be used properly
  # by dgCMatrix
  counts <- assay(altExp(inSCE, newAssayName), newAssayName)
  counts[is.na(counts)] <- 0

  #store back counts
  #assay(altExp(inSCE, newAssayName), "altExp") <- counts
  assay(altExp(inSCE, newAssayName), newAssayName) <- counts

  return(inSCE)
}

#' runSeuratFindMarkers
#'
#' @param inSCE Input \code{SingleCellExperiment} object.
#' @param cells1 A \code{list} of sample names included in group1.
#' @param cells2 A \code{list} of sample names included in group2.
#' @param group1 Name of group1.
#' @param group2 Name of group2.
#' @param allGroup Name of all groups.
#' @param conserved Logical value indicating if markers conserved between two
#' groups should be identified. Default is \code{FALSE}.
#' @param test Test to use for DE. Default \code{"wilcox"}.
#' @param onlyPos Logical value indicating if only positive markers should be
#' returned.
#' @param minPCT Numeric value indicating the minimum fraction of min.pct
#'  cells in which genes are detected. Default is \code{0.1}.
#' @param threshUse Numeric value indicating the logFC threshold value on
#'  which on average, at least X-fold difference (log-scale) between the
#'  two groups of cells exists. Default is \code{0.25}.
#' @param verbose Logical value indicating if informative messages should
#'  be displayed. Default is \code{TRUE}.
#' @return A \code{SingleCellExperiment} object that contains marker genes
#' populated in a data.frame stored inside metadata slot.
#' @export
runSeuratFindMarkers <- function(inSCE,
                                 cells1 = NULL,
                                 cells2 = NULL,
                                 group1 = NULL,
                                 group2 = NULL,
                                 allGroup = NULL,
                                 conserved = FALSE,
                                 test = "wilcox",
                                 onlyPos = FALSE,
                                 minPCT = 0.1,
                                 threshUse = 0.25,
                                 verbose = TRUE) {
  seuratObject <- convertSCEToSeurat(inSCE)
  seurat.version <- .getSeuratObjectMajorVersion(seuratObject)
  markerGenes <- NULL
  if (is.null(allGroup)
      && (!is.null(group1) && !is.null(group2))) {
    #convert (_) to (-) as required by Seurat
    cells1 <- .convertToHyphen(cells1)
    cells2 <- .convertToHyphen(cells2)
    if(seurat.version >= 5.0){
      cells1 <- unlist(cells1)
      cells2 <- unlist(cells2)
    }
    Seurat::Idents(seuratObject, cells = cells1) <- group1
    Seurat::Idents(seuratObject, cells = cells2) <- group2
    markerGenes <- NULL
    if (!conserved) {
      markerGenes <- Seurat::FindMarkers(
        object = seuratObject,
        ident.1 = group1,
        ident.2 = group2,
        test.use = test,
        only.pos = onlyPos
      )
    }
    else{
      seuratObject[["groups"]] <- Seurat::Idents(seuratObject)
      markerGenes <- .findConservedMarkers(
        object = seuratObject,
        ident.1 = group1,
        ident.2 = group2,
        grouping.var = "groups",
        test.use = test,
        only.pos = onlyPos,
        cells1 = cells1,
        cells2 = cells2
      )
    }
    markerGenes$cluster1 <- group1
    markerGenes$cluster2 <- group2
    gene.id <- rownames(markerGenes)
    markerGenes <- cbind(gene.id, markerGenes)
  }
  else if (!is.null(allGroup)
           && (is.null(group1) && is.null(group2))) {
    Seurat::Idents(seuratObject,
                   cells = colnames(seuratObject)) <-
      colData(inSCE)[[allGroup]]
    markerGenes <- Seurat::FindAllMarkers(
      seuratObject,
      test.use = test,
      only.pos = onlyPos,
      logfc.threshold = threshUse,
      min.pct = minPCT,
      verbose = verbose
    )
    gene.id <- markerGenes$gene
    markerGenes <- cbind(gene.id, markerGenes)
    markerGenes$gene <- NULL
    # grp <- unique(colData(inSCE)[[allGroup]])
    # clust <- as.integer(unique(Seurat::Idents(seuratObject)))
    # for(i in seq(length(clust))){
    #   levels(markerGenes$cluster)[clust[i]] <- grp[i]
    # }
    colnames(markerGenes)[which(colnames(markerGenes) == "cluster")] <-
      "cluster1"
    markerGenes$cluster2 <- rep("all", nrow(markerGenes))
  }
  else if (is.null(allGroup)
           && (is.null(group1) && is.null(group2))) {
    Seurat::Idents(seuratObject,
                   cells = colnames(seuratObject)) <-
      colData(inSCE)[[S4Vectors::metadata(inSCE)$seurat$clusterName]]
    markerGenes <- Seurat::FindAllMarkers(
      seuratObject,
      test.use = test,
      only.pos = onlyPos,
      logfc.threshold = threshUse,
      min.pct = minPCT,
      verbose = verbose
    )
    gene.id <- markerGenes$gene
    markerGenes <- cbind(gene.id, markerGenes)
    markerGenes$gene <- NULL
    # grp <- unique(colData(inSCE)[[S4Vectors::metadata(inSCE)$seurat$clusterName]])
    # clust <- as.integer(unique(Seurat::Idents(seuratObject)))
    # for(i in seq(length(clust))){
    #   levels(markerGenes$cluster)[clust[i]] <- grp[i]
    # }
  }
  rownames(markerGenes) <- NULL
  S4Vectors::metadata(inSCE)$seuratMarkers <- markerGenes
  return(inSCE)
}

#' Compute and plot visualizations for marker genes
#'
#' @param inSCE Input \code{SingleCellExperiment} object.
#' @param useAssay Specify the name of the assay that will be scaled by this
#'  function.
#' @param plotType Specify the type of the plot to compute. Options are limited
#' to "ridge", "violin", "feature", "dot" and "heatmap".
#' @param features Specify the features to compute the plot against.
#' @param groupVariable Specify the column name from the colData slot that
#' should be used as grouping variable.
#'  Default is \code{NULL}.
#' @param reducedDimName Specify the name of the dimensional reduction to be used.
#' Default is "seuratNormData".
#' @param splitBy Specify the column name from the colData slot that should be
#' used to split samples.
#'  Default is \code{NULL}.
#' @param reducedDimName saved dimension reduction name in the
#' SingleCellExperiment object. Default \code{seuratUMAP}.
#' @param cols Specify two colors to form a gradient between. Default is
#' \code{c("lightgrey", "blue")}.
#' @param ncol Visualizations will be adjusted in "ncol" number of columns.
#'  Default is \code{1}.
#' @param combine A logical value that indicates if the plots should be combined
#'  together into a single plot if \code{TRUE}, else if \code{FALSE} returns
#'  separate ggplot objects for each feature. Only works when \code{plotType}
#'  parameter is \code{"feature"}, \code{"violin"} or \code{"ridge"}. For
#'  \code{"heatmap"} and \code{"dot"}, plots for all features are always
#'  combined into a single plot. Default \code{FALSE}.
#' @return Plot object
#' @export
plotSeuratGenes <- function(inSCE,
                            useAssay = "seuratNormData",
                            plotType,
                            features,
                            groupVariable = NULL,
                            reducedDimName = "seuratUMAP",
                            splitBy = NULL,
                            cols = c("lightgrey", "blue"),
                            ncol = 1,
                            combine = FALSE) {
    plotType <- match.arg(plotType, c("dot", "feature", "ridge", "heatmap", "violin"))

    #setup seurat object and the corresponding groups
    seuratObject <- convertSCEToSeurat(inSCE, normAssay = useAssay, copyReducedDim = TRUE)
    seurat.version <- .getSeuratObjectMajorVersion(seuratObject)

    if(plotType %in% c("dot", "heatmap")){
        if (length(features) == 1) {
            dummyfeature <- rownames(inSCE)[which(!rownames(inSCE) == features)][1]
            seuratObject <-
                Seurat::ScaleData(seuratObject, features = c(features,dummyfeature))
        }else{
            seuratObject <-
                Seurat::ScaleData(seuratObject, features = features)
        }
    }

    indices <- list()
    cells <- list()
    if(!is.null(groupVariable)){
        groups <- unique(colData(inSCE)[[groupVariable]])
        for (i in seq(length(groups))) {
            indices[[i]] <- which(colData(inSCE)[[groupVariable]] == groups[i],
                                  arr.ind = TRUE)
            cells[[i]] <- colnames(inSCE)[indices[[i]]]
            cells[[i]] <- .convertToHyphen(cells[[i]])
            if(seurat.version >= 5.0){
                cells[[i]] <- unlist(cells[[i]])
            }
            Seurat::Idents(seuratObject, cells = cells[[i]]) <- groups[i]
        }
    }

    if (!is.null(splitBy)) {
        seuratObject[[splitBy]] <- colData(inSCE)[[splitBy]]
    }

    #plot required visualization
    if (plotType == "ridge") {
        return(
            Seurat::RidgePlot(
                seuratObject,
                features = features,
                ncol = ncol,
                combine = combine
            )
        )
    }
    else if (plotType == "violin") {
        return(
            Seurat::VlnPlot(
                seuratObject,
                features = features,
                ncol = ncol,
                split.by = splitBy,
                combine = combine
            )
        )
    }
    else if (plotType == "feature") {
        return(
            Seurat::FeaturePlot(
                seuratObject,
                features = features,
                cols = cols,
                ncol = ncol,
                split.by = splitBy,
                combine = combine,
                reduction = reducedDimName
            )
        )
    }
    else if (plotType == "dot") {
        return(Seurat::DotPlot(
            seuratObject,
            features = unique(features),
            split.by = splitBy
        ))
    }
    else if (plotType == "heatmap") {
        return(Seurat::DoHeatmap(seuratObject, features = features))
    }
}


.findConservedMarkers <- function(object,
                                  ident.1,
                                  ident.2,
                                  grouping.var = "groups",
                                  test.use,
                                  only.pos,
                                  cells1,
                                  cells2) {
  meta.method <- metap::minimump
  verbose <- TRUE
  slot <- "data"
  assay <- "RNA"
  marker.test <- list()

  object.var <-
    Seurat::FetchData(object = object, vars = grouping.var)
  levels.split <- names(x = sort(x = table(object.var[, 1])))
  num.groups <- length(levels.split)

  marker.test[[1]] <- Seurat::FindMarkers(
    object = object,
    assay = assay,
    slot = slot,
    ident.1 = levels.split[1],
    ident.2 = levels.split[2],
  )

  marker.test[[2]] <- Seurat::FindMarkers(
    object = object,
    assay = assay,
    slot = slot,
    ident.1 = levels.split[2],
    ident.2 = levels.split[3],
  )

  marker.test[[3]] <- Seurat::FindMarkers(
    object = object,
    assay = assay,
    slot = slot,
    ident.1 = levels.split[3],
    ident.2 = levels.split[1],
  )

  names(x = marker.test)[1] <- levels.split[1]
  names(x = marker.test)[2] <- levels.split[2]
  names(x = marker.test)[3] <- levels.split[3]

  marker.test <- Filter(f = Negate(f = is.null), x = marker.test)
  genes.conserved <- Reduce(f = intersect,
                            x = lapply(
                              X = marker.test,
                              FUN = function(x) {
                                return(rownames(x = x))
                              }
                            ))
  markers.conserved <- list()
  for (i in seq_len(length(x = marker.test))) {
    markers.conserved[[i]] <- marker.test[[i]][genes.conserved,]
    colnames(x = markers.conserved[[i]]) <- paste(names(x = marker.test)[i],
                                                  colnames(x = markers.conserved[[i]]),
                                                  sep = "_")
  }
  markers.combined <- Reduce(cbind, markers.conserved)
  pval.codes <-
    colnames(x = markers.combined)[grepl(pattern = "*_p_val$", x = colnames(x = markers.combined))]
  if (length(x = pval.codes) > 1) {
    markers.combined$max_pval <- apply(X = markers.combined[, pval.codes, drop = FALSE],
                                       MARGIN = 1,
                                       FUN = max)
    combined.pval <- data.frame(cp = apply(
      X = markers.combined[, pval.codes, drop = FALSE],
      MARGIN = 1,
      FUN = function(x) {
        return(meta.method(x)$p)
      }
    ))
    meta.method.name <- as.character(x = formals()$meta.method)
    if (length(x = meta.method.name) == 3) {
      meta.method.name <- meta.method.name[3]
    }
    colnames(x = combined.pval) <-
      paste0(meta.method.name, "_p_val")
    markers.combined <- cbind(markers.combined, combined.pval)
    markers.combined <-
      markers.combined[order(markers.combined[, paste0(meta.method.name, "_p_val")]),]
  }
  lfcCol <-
    colnames(markers.combined)[grep(paste0(ident.1, "_avg"), colnames(markers.combined))]
  markers.combined <- markers.combined[, c(
    paste0(ident.1, "_p_val"),
    lfcCol,
    paste0(ident.1, "_pct.1"),
    paste0(ident.1, "_pct.2"),
    paste0("_p_val")
  )]
  colnames(markers.combined) <- gsub(
    pattern = paste0(ident.1, "_"),
    replacement = "",
    x = colnames(markers.combined)
  )
  colnames(markers.combined) <-
    c(colnames(markers.combined)[-length(colnames(markers.combined))], "p_val_adj")
  return(markers.combined)
}

#' Get variable feature names after running runSeuratFindHVG function
#'
#' @param inSCE Input \code{SingleCellExperiment} object.
#'
#' @return A list of variable feature names.
#' @export
getSeuratVariableFeatures <- function(inSCE) {
  obj <- S4Vectors::metadata(inSCE)$seurat$obj
  result <- NULL
  if (!is.null(obj)) {
    if(methods::is(obj, "list")) {
      result <- obj$RNA$var.features
    }
    if (methods::is(obj, "Seurat")) {
      result <- Seurat::VariableFeatures(obj)
    }
  }
  return(result)
}
