#' Fusion centroid
#'
#' `centroid_fusion` calculates the centroid of each timestep in fusion events.
#' The function expects an edge-list of fusion events identified by
#' [fusion_id()] from edge-lists generated with [edge_dist()] and a `data.table`
#' with relocation data appended with a `timegroup` column from [group_times()].
#' Relocation data should be in two columns representing the X and Y
#' coordinates, or in a geometry column prepared by the helper function
#' [get_geometry()].
#'
#' The `edges` and `DT` must be `data.table`s. If your data is a `data.frame`,
#' you can convert it by reference using [data.table::setDT()] or by reassigning
#' using [data.table::data.table()].
#'
#' The `edges` and `DT` are internally merged in this function using the columns
#' `timegroup` (from [group_times()]) and `ID1` and `ID2` (in `edges`, from
#' `dyad_id`) and `id` (in `DT`). This function expects a `fusionID` present,
#' generated with the [fusion_id()] function. The `timegroup` argument expects
#' the names of a column in `edges` which correspond to the timegroup column.
#' The `id` and `timegroup` arguments expect the names of columns in `DT` which
#' correspond to the id, and timegroup columns.
#'
#' See below under "Interface" for details on providing coordinates and under
#' "Centroid function" for details on the underlying centroid function used.
#'
#' @inheritSection centroid_group Interface
#' @inheritSection centroid_group Centroid function
#'
#' @param edges edge-list generated generated by [edge_dist()] or
#'  [edge_nn()], with fusionID column generated by [fusion_id()]
#' @param DT input data.table with timegroup column generated with
#'   [group_times()] matching the input data.table used to generate the edge
#'   list with [edge_nn()] or [edge_dist()]
#' @inheritParams group_pts
#' @inheritParams centroid_group
#'
#' @return `centroid_fusion` returns the input `edges` appended with
#'  centroid column(s) for each timestep and fusion id.
#'
#'  If `coords` are provided, the centroid columns will be named by prefixing
#'  the coordinate column names with "centroid_" (eg. "X" = "centroid_X"). If
#'  `geometry` is used, the centroid column will be named "centroid".
#'
#'  Note: due to the merge required within this function, the output needs to be
#'  reassigned unlike some other `spatsoc` functions like `dyad_id` and
#'  `group_pts`. See details in
#'  [FAQ](https://docs.ropensci.org/spatsoc/articles/faq.html).
#'
#'  A message is returned when the centroid column(s) already exist in the input
#'  because they will be overwritten.
#'
#' @export
#' @family Centroid functions
#' @seealso [fusion_id] [edge_dist] [group_pts]
#' @examples
#' # Load data.table
#' library(data.table)
#' \dontshow{data.table::setDTthreads(1)}
#' # Read example data
#' DT <- fread(system.file("extdata", "DT.csv", package = "spatsoc"))
#' \dontshow{
#' # (Subset example data to reduce runtime on CRAN)
#' if (isFALSE(rlang::is_interactive())) {
#'   DT <- DT[as.Date(datetime) == '2017-01-17']
#' }
#' }
#' # Cast the character column to POSIXct
#' DT[, datetime := as.POSIXct(datetime, tz = 'UTC')]
#'
#' # Temporal grouping
#' group_times(DT, datetime = 'datetime', threshold = '20 minutes')
#'
#' # Edge-list generation
#' edges <- edge_dist(
#'     DT,
#'     threshold = 100,
#'     id = 'ID',
#'     coords = c('X', 'Y'),
#'     timegroup = 'timegroup',
#'     returnDist = TRUE,
#'     fillNA = FALSE
#'   )
#'
#' # Generate dyad id
#' dyad_id(edges, id1 = 'ID1', id2 = 'ID2')
#'
#' # Generate fusion id
#' fusion_id(edges, threshold = 100)
#'
#' # Calculate fusion centroid
#' centroids <- centroid_fusion(
#'   edges,
#'   DT,
#'   id = 'ID',
#'   coords = c('X', 'Y'),
#'   timegroup = 'timegroup'
#' )
#'
#' print(centroids)
#'
#' # Or, using the new geometry interface
#' get_geometry(DT, coords = c('X', 'Y'), crs = 32736)
#' edges <- edge_dist(DT, threshold = 100, id = 'ID', timegroup = 'timegroup', returnDist = TRUE)
#' dyad_id(edges, id = 'ID1', id2 = 'ID2')
#' fusion_id(edges, threshold = 100)
#' centroids <- centroid_fusion(
#'   edges,
#'   DT,
#'   id = 'ID',
#'   timegroup = 'timegroup'
#' )
#' print(centroids)
centroid_fusion <- function(
    edges = NULL,
    DT = NULL,
    id = NULL,
    coords = NULL,
    crs = NULL,
    timegroup = 'timegroup',
    geometry = 'geometry') {

  centroid_(
    centroid_groupings = 'fusionID',
    edges = edges,
    DT = DT,
    id = id,
    coords = coords,
    crs = crs,
    timegroup = timegroup,
    geometry = geometry
  )
}

# Internal function reused for centroid_dyad and centroid_fusion
centroid_ <- function(
    centroid_groupings = NULL,
    edges = NULL,
    DT = NULL,
    id = NULL,
    coords = NULL,
    crs = NULL,
    timegroup = 'timegroup',
    geometry = 'geometry') {

  # Due to NSE notes in R CMD check
  centroid <- geo <- . <- x <- y <- NULL

  assert_not_null(centroid_groupings)

  assert_not_null(DT)
  assert_is_data_table(DT)

  assert_not_null(edges)
  assert_not_null(id)
  assert_not_null(timegroup)

  check_cols_edges <- c(centroid_groupings, 'ID1', 'ID2', timegroup)
  assert_are_colnames(edges, check_cols_edges)
  check_cols_DT <- c(id, timegroup)
  assert_are_colnames(DT, check_cols_DT)

  if (is.null(coords)) {
    m <- merge(edges,
               DT[, .SD, .SDcols = c(geometry, id, 'timegroup')],
               by.x = c('ID1', timegroup),
               by.y = c(id, timegroup),
               all.x = TRUE,
               sort = FALSE)

    use_mean <- crs_use_mean(sf::st_crs(DT[[geometry]]))

    m[!is.na(centroid_groupings),
      centroid := calc_centroid(geometry = geo, use_mean = use_mean),
      env = list(geo = geometry,
                 centroid_groupings = centroid_groupings,
                 timegroup = timegroup),
      by = .(centroid_groupings, timegroup)]
    m[, centroid := sf::st_sfc(centroid, recompute_bbox = TRUE)]

  } else {
    if (is.null(crs)) {
      crs <- sf::NA_crs_
    }

    assert_are_colnames(DT, coords)
    assert_length(coords, 2)
    assert_col_inherits(DT, coords, 'numeric')

    xcol <- data.table::first(coords)
    ycol <- data.table::last(coords)

    out_xcol <- paste0('centroid_', gsub(' ', '', xcol))
    out_ycol <- paste0('centroid_', gsub(' ', '', ycol))

    m <- merge(edges,
               DT[, .SD, .SDcols = c(coords, id, 'timegroup')],
               by.x = c('ID1', timegroup),
               by.y = c(id, timegroup),
               all.x = TRUE,
               sort = FALSE)

    if (out_xcol %in% colnames(m)) {
      message(paste(out_xcol, 'column will be overwritten by this function'))
      data.table::set(m, j = out_xcol, value = NULL)
    }

    if (out_ycol %in% colnames(m)) {
      message(paste(out_ycol, 'column will be overwritten by this function'))
      data.table::set(m, j = out_ycol, value = NULL)
    }

    use_mean <- crs_use_mean(crs)

    m[!is.na(centroid_groupings),
      (c(out_xcol, out_ycol)) :=
        calc_centroid(, x, y, crs = crs, use_mean = use_mean),
      env = list(x = xcol, y = ycol,
                 centroid_groupings = centroid_groupings,
                 timegroup = timegroup),
      by = .(centroid_groupings, timegroup)]

    data.table::set(m, j = coords, value = NULL)
  }

  data.table::setcolorder(m, colnames(edges))
  return(m[])
}
