##' Combining or subsetting poplin objects
##'
##' Methods to combine multiple \linkS4class{poplin} objects by row or column,
##' or to subset a \linkS4class{poplin} object by row or column.
##'
##' @section Combining:
##'
##' In the code snippets below, \code{...} represents multiple
##' \linkS4class{poplin} objects to be combined.
##'
##' \describe{
##' \item{\code{rbind(..., deparse.level = 1)}:}{
##'
##' Take a sequence of poplin objects in \code{...} and combine by rows. Note
##' that all objects in ... must have the exact same values for
##' [poplin_data_list]. See \code{?}[rbind] for the interpretation of
##' \code{deparse.level}.
##'
##' }
##' \item{\code{cbind(..., deparse.level = 1)}:}{
##'
##' Take a sequence of poplin objects in \code{...} and combine by columns. Note
##' that all objects in ... must have the same values of [poplin_reduced_names].
##' Dimension reduction results with the same name across the objects will be
##' combined row-wise to create the corresponding entry in the output object.
##' See \code{?}[cbind] for the interpretation of \code{deparse.level}.
##'
##' }
##' }
##'
##' Refer to \code{help("SummarizedExperiment-class", package =
##' "SummarizedExperiment")} for details on how \code{rowData}, \code{colData},
##' and \code{metadata} are combined in the output object.
##'
##' @section Subsetting:
##'
##' In the code snippets below, \code{x} is a \linkS4class{poplin}
##' object.
##'
##' \describe{
##'
##' \item{\code{x[i, j]}, \code{x[i, j] <- value}:}{\code{i} and \code{j} are
##' indices specifying the rows and columns to extract or replace. Indices can
##' be a logical, integer, character vector, or empty (missing). \code{value}
##' must be a poplin object with the dimension and assay elements consistent
##' with the subset \code{x[i, j]} being replaced. \code{value} is also expected
##' to have the same name and order of [poplin_data_names] (and
##' [poplin_reduced_names] as well if \code{j} is specified) as \code{x}.}
##'
##' }
##'
##' @importFrom BiocGenerics rbind cbind
##' @importFrom SummarizedExperiment Assays
##' @name poplin-combine/subset
##' @docType methods
NULL

## Need to think about how to handle metadata of poplinData(x) and
## poplinReducedData(x) when data are combined
## poplin class cbind and rbind
setMethod("rbind", "poplin", function(..., deparse.level = 1) {
  old_validity <- S4Vectors:::disableValidity()
  if (!isTRUE(old_validity)) {
    S4Vectors:::disableValidity(TRUE)
    on.exit(S4Vectors:::disableValidity(old_validity))
  }
  out <- callNextMethod()
  args <- list(...)
  ## Utilize Assay class for combining objects
  ## poplinData_assays <- lapply(args, function(x) .poplin_to_assays(x))
  ## tryCatch({
  ##   combined <- do.call(rbind, poplinData_assays)
  ##   poplinData_all <- do.call(
  ##     DataFrame, c(lapply(combined@data, I),
  ##                  list(row.names = NULL, check.names = FALSE))
  ##   )
  ## },
  ## error = function(err) {
  ##   stop(
  ##     "failed to combine 'poplinData' in 'rbind(<",
  ##     class(args[[1]]), ">)':\n  ", conditionMessage(err)
  ##   )
  ## })
  tryCatch({
    poplinData_all <- do.call(rbind, lapply(args, poplinData))
    ## how to handle metadata?
    metadata(poplinData_all) <- list() # clean metadata; consistency with assay-via method
  })
  ## Utilize SE class for combining objects: see the SingleCellExperiment source
  poplinReducedData_se <- lapply(args, function(x) .poplin_to_se_coldata(x))
  tryCatch({
    poplinReducedData_all <- colData(do.call(rbind, poplinReducedData_se))
  },
  error = function(err) {
    stop(
      "failed to combine 'poplinReducedData' in 'rbind(<",
      class(args[[1]]), ">)':\n  ", conditionMessage(err)
    )
  })
  ## Skip validity checks with check = FALSE for efficiency as modification
  ## cannot alter the validity of object
  BiocGenerics:::replaceSlots(out, poplinData = poplinData_all,
                              poplinReducedData = poplinReducedData_all,
                              check = FALSE)
})

##' @rdname poplin-combine/subset
setMethod("cbind", "poplin", function(..., deparse.level = 1) {
  old_validity <- S4Vectors:::disableValidity()
  if (!isTRUE(old_validity)) {
    S4Vectors:::disableValidity(TRUE)
    on.exit(S4Vectors:::disableValidity(old_validity))
  }
  out <- callNextMethod()
  args <- list(...)
  poplinData_assays <- lapply(args, function(x) .poplin_to_assays(x))
  tryCatch({
    combined <- do.call(cbind, poplinData_assays)
    poplinData_all <- do.call(
      DataFrame, c(lapply(combined@data, I),
                   list(row.names = NULL, check.names = FALSE))
    )
    ## how to hand metadata?
    ## metadata(poplinData_all) <- metadata(poplinData(args[[1]]))
  },
  error = function(err) {
    stop(
      "failed to combine 'poplinData' in 'cbind(<",
      class(args[[1]]), ">)':\n  ", conditionMessage(err)
    )
  })
  tryCatch({
    poplinReducedData_all <- do.call(rbind, lapply(args, poplinReducedData))
  },
  error = function(err) {
    stop(
      "failed to combine 'poplinReducedData' in 'cbind(<",
      class(args[[1]]), ">)':\n  ", conditionMessage(err)
    )
  })
  ## Skip validity checks with check = FALSE for efficiency as modification
  ## cannot alter the validity of object
  BiocGenerics:::replaceSlots(out, poplinData = poplinData_all,
                              poplinReducedData = poplinReducedData_all,
                              check = FALSE)
})