#' Directional correlation delay based edge-lists
#'
#' `edge_delay` returns edge-lists defined by the directional correlation
#' delay between individuals. The function expects a distance based edge-list
#' generated by `edge_dist` or `edge_nn`, a `data.table` with
#' relocation data, individual identifiers and a window argument. The window
#' argument is used to specify the temporal window within which to measure the
#' directional correlation delay. Relocation data should be in two columns
#' representing the X and Y coordinates.
#'
#' 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()].
#'
#' The `edges` argument expects a distance based edge-list generated with
#' `edge_nn` or `edge_dist`. The `DT` argument expects relocation
#' data with a timegroup column generated with `group_times`.
#'
#' The rows in `edges` and `DT` are internally matched in
#' `edge_delay` using the columns `timegroup` (from
#' `group_times`) and `ID1` and `ID2` (in `edges`, from
#' `dyad_id`) with `id` (in `DT`). This function expects a
#' `fusionID` present, generated with the `fusion_id` function, and a
#' `dyadID` present, generated with the `dyad_id` function. The
#' `id`, and `direction` arguments expect the names of a column in
#' `DT` which correspond to the id, and direction columns.
#'
#' @inheritParams centroid_fusion
#' @inheritParams direction_group
#' @param window temporal window in unit of timegroup column generated with
#'   `group_times`, eg. `window = 4` corresponds to the 4 timegroups
#'   before and after the focal observation
#'
#' @return `edge_delay` returns the input `edges` appended with a
#'   'direction_delay' column indicating the temporal delay (in units of
#'   timegroups) at which ID1's direction of movement is most similar to ID2's
#'   direction of movement, within the temporal window defined, and a
#'   'direction_diff' column indicating the absolute difference in direction.
#'   For example, if focal individual 'A' moves in a 45 degree direction at time
#'   2 and individual 'B' moves in a most similar direction within the window at
#'   time 5, the directional correlation delay between A and B is 3. Positive
#'   values of directional correlation delay indicate a directed leadership edge
#'   from ID1 to ID2.
#'
#'  Note: due to the merge required within this function, the output needs to be
#'  reassigned unlike some other `spatsoc` functions like `dyad_id`.
#'  See details in [FAQ](https://docs.ropensci.org/spatsoc/articles/faq.html).
#'
#' @export
#'
#' @references
#'
#' The directional correlation delay is defined in Nagy et al. 2010
#' (\doi{doi:10.1038/nature08891}).
#'
#' See examples of measuring the directional correlation delay:
#'  * \doi{doi:10.1016/j.anbehav.2013.07.005}
#'  * \doi{doi:10.1073/pnas.1305552110}
#'  * \doi{doi:10.1111/jfb.15315}
#'  * \doi{doi:10.1371/journal.pcbi.1003446}
#'
#' @family Edge-list generation
#' @family Direction functions
#'
#' @examples
#' # Load data.table
#' library(data.table)
#' \dontshow{data.table::setDTthreads(1)}
#'
#' # Read example data
#' DT <- fread(system.file("extdata", "DT.csv", package = "spatsoc"))
#'
#' # Select only individuals A, B, C for this example
#' DT <- DT[ID %in% c('A', 'B', 'C')]
#'
#' # Cast the character column to POSIXct
#' DT[, datetime := as.POSIXct(datetime, tz = 'UTC')]
#'
#' # Temporal grouping
#' group_times(DT, datetime = 'datetime', threshold = '20 minutes')
#'
#' # Calculate direction
#' direction_step(
#'   DT = DT,
#'   id = 'ID',
#'   coords = c('X', 'Y'),
#'   crs = 32736
#' )
#'
#' # Distance based 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)
#'
#' # Directional correlation delay
#' delay <- edge_delay(
#'   edges = edges,
#'   DT = DT,
#'   window = 3,
#'   id = 'ID'
#' )
#'
#' delay[, mean(direction_delay, na.rm = TRUE), by = .(ID1, ID2)][V1 > 0]
#'
#' # Or, using the new geometry interface
#' get_geometry(DT, coords = c('X', 'Y'), crs = 32736)
#' direction_step(DT, id = 'ID')
#' edges <- edge_dist(DT, threshold = 100, id = 'ID', timegroup = 'timegroup', returnDist = TRUE)
#' dyad_id(edges, id = 'ID1', id2 = 'ID2')
#' fusion_id(edges, threshold = 100)
#' delay <- edge_delay(
#'   edges = edges,
#'   DT = DT,
#'   window = 3,
#'   id = 'ID'
#' )
edge_delay <- function(
    edges,
    DT,
    window = NULL,
    id = NULL,
    direction = 'direction') {
  # due to NSE notes in R CMD check
  . <- timegroup <- fusionID <- timegroup_min <- timegroup_max <-
    timegroup_delay <- ID1  <- ID2 <- direction_delay <- direction_diff <-
    dyadID <- NULL

  assert_not_null(DT)
  assert_is_data_table(DT)
  assert_not_null(edges)
  assert_not_null(id)

  check_cols_edges <- c('ID1', 'ID2', 'timegroup')
  assert_are_colnames(edges, check_cols_edges)

  check_cols_DT <- c(id, 'timegroup', direction)
  assert_are_colnames(DT, check_cols_DT)

  assert_not_null(window)
  assert_inherits(window, 'numeric')

  assert_col_inherits(DT, 'timegroup', 'integer')
  assert_col_inherits(edges, 'timegroup', 'integer')

  assert_are_colnames(edges, 'fusionID', ', did you run fusion_id?')
  assert_are_colnames(edges, 'dyadID', ', did you run dyad_id?')

  drop_nas <- data.table::copy(edges)[
    !(is.na(fusionID) | is.na(ID1) | is.na(ID2) | is.na(dyadID))]

  # "Forward": all edges ID1 -> ID2
  forward <- drop_nas[ID1 == tstrsplit(dyadID, '-')[[1L]]]

  forward[, timegroup_min :=
            data.table::fifelse(timegroup - window < min(timegroup),
                                min(timegroup),
                                timegroup - window),
          by = fusionID,
          env = list(window = window)]

  forward[, timegroup_max :=
            data.table::fifelse(timegroup + window > max(timegroup),
                                max(timegroup),
                                timegroup + window),
          by = fusionID,
          env = list(window = window)]

  forward[, c('timegroup_delay', 'direction_diff') := {
    focal_direction <- DT[timegroup == .BY$timegroup &
                            id == ID1, direction]
    sub <- DT[between(timegroup, timegroup_min, timegroup_max) & id == ID2,
              .(timegroup, diff = diff_rad(focal_direction, direction))]
    sub[which.min(diff)]
  },
  by = c('timegroup', 'dyadID'),
  env = list(id = id, direction = direction)]

  forward[, direction_delay := timegroup_delay - timegroup]

  data.table::set(forward,
                  j = c('timegroup_min', 'timegroup_max','timegroup_delay'),
                  value = NULL)

  # "Reverse": replicate forward but reverse direction ID1 <- ID2
  reverse <- data.table::copy(forward)
  data.table::setnames(reverse, c('ID1', 'ID2'), c('ID2', 'ID1'))
  reverse[, direction_delay := - direction_delay]

  out <- data.table::rbindlist(list(
    forward,
    reverse
  ), use.names = TRUE)

  data.table::setorder(out, timegroup)
  data.table::setcolorder(
    out,
    c('timegroup', 'ID1', 'ID2', 'dyadID', 'fusionID')
  )

  return(out)
}

