#' Build polygons
#'
#' `build_polys` generates a simple feature collection with POLYGONs from a
#' `data.table`. The function expects a `data.table` with
#' relocation data, individual identifiers, a crs,
#' home range type and parameters. The relocation
#' data is transformed into POLYGONs using either [adehabitatHR::mcp] or
#' [adehabitatHR::kernelUD] for each individual and, optionally,
#' combination of columns listed in `splitBy`. Relocation data should be in two
#' columns representing the X and Y coordinates.
#'
#' [group_polys] uses `build_polys` for grouping overlapping
#' polygons created from relocations.
#'
#' ## R-spatial evolution
#'
#' Please note, spatsoc has followed updates from R spatial, GDAL and PROJ for
#' handling coordinate reference systems, see more below and  details at
#' <https://r-spatial.org/r/2020/03/17/wkt.html>.
#'
#' In addition, `build_polys` previously used [sp::SpatialPoints] but has been
#' updated to use [sf::st_as_sf] according to the R-spatial evolution, see more
#' at <https://r-spatial.org/r/2022/04/12/evolution.html>.
#'
#' ## Notes on arguments
#'
#' The `DT` must be a `data.table`. If your data is a `data.frame`, you can
#' convert it by reference using [data.table::setDT].
#'
#' The `id`, `coords` (and optional `splitBy`) arguments
#' expect the names of respective columns in `DT` which correspond
#' to the individual identifier, X and Y coordinates, and additional
#' grouping columns.
#'
#' The `crs` argument expects a character string or numeric
#' defining the coordinate reference system to be passed to [sf::st_crs].
#' For example, for UTM zone 36S (EPSG 32736), the crs
#' argument is `crs = "EPSG:32736"` or `crs = 32736`.
#' See <https://spatialreference.org>
#' for a list of EPSG codes.
#'
#' The `hrType` must be either one of "kernel" or "mcp". The
#' `hrParams` must be a named list of arguments matching those
#' of [adehabitatHR::kernelUD] and [adehabitatHR::getverticeshr]
#' or [adehabitatHR::mcp].
#'
#' The `splitBy` argument offers further control building
#' POLYGONs. If in your `DT`, you have multiple
#' temporal groups (e.g.: years) for example, you can provide the
#' name of the column which identifies them and build POLYGONs
#' for each individual in each year.
#'
#'
#' @return `build_polys` returns a simple feature collection with POLYGONs
#' for each individual (and optionally `splitBy` combination).
#'
#' An error is returned when `hrParams` do not match the arguments
#' of the respective `hrType` `adehabitatHR` function.
#'
#'
#' @inheritParams build_lines
#' @inheritParams group_polys
#' @param spPts alternatively, provide solely a SpatialPointsDataFrame with one
#' column representing the ID of each point, as specified by [adehabitatHR::mcp]
#' or [adehabitatHR::kernelUD]
#' @param crs numeric or character defining the coordinate reference
#'   system to be passed to [sf::st_crs]. For example, either
#'   `crs = "EPSG:32736"` or `crs = 32736`.
#' @export
#'
#' @family Build functions
#' @seealso [group_polys]
#'
#' @examples
#' # Load data.table
#' library(data.table)
#' \dontshow{data.table::setDTthreads(1)}
#'
#' # Read example data
#' DT <- fread(system.file("extdata", "DT.csv", package = "spatsoc"))
#'
#' # Cast the character column to POSIXct
#' DT[, datetime := as.POSIXct(datetime, tz = 'UTC')]
#'
#' # EPSG code for example data
#' utm <- 32736
#'
#' # Build polygons for each individual using kernelUD and getverticeshr
#' build_polys(DT, crs = utm, hrType = 'kernel',
#'             hrParams = list(grid = 60, percent = 95),
#'             id = 'ID', coords = c('X', 'Y'))
#'
#' # Build polygons for each individual by year
#' DT[, yr := year(datetime)]
#' build_polys(DT, crs = utm, hrType = 'mcp',
#'             hrParams = list(percent = 95),
#'             id = 'ID', coords = c('X', 'Y'), splitBy = 'yr')
build_polys <- function(
    DT = NULL,
    crs = NULL,
    hrType = NULL,
    hrParams = NULL,
    id = NULL,
    coords = NULL,
    splitBy = NULL,
    spPts = NULL,
    projection = NULL) {
  # due to NSE notes in R CMD check
  . <- NULL

  if (!is.null(projection)) {
    warning('projection argument is deprecated, setting crs = projection')
    crs <- projection
  }

  if (is.null(DT) && is.null(spPts)) {
    stop('input DT or spPts required')
  }

  if (!is.null(DT) && !is.null(spPts)) {
    stop('cannot provide both DT and spPts')
  }

  if (!is.null(DT) && is.null(spPts)) {
    assert_not_null(coords)
    assert_not_null(id)
    assert_not_null(crs)
    assert_length(coords, 2)

    if (is.null(splitBy)) {
      splitBy <- id
    } else {
      splitBy <- c(id, splitBy)
    }

    assert_are_colnames(DT, c(splitBy, coords))
  }

  assert_col_inherits(DT, coords, 'numeric')
  assert_col_inherits(DT, splitBy, c('numeric', 'character', 'integer'))
  assert_not_null(hrType)

  if (is.null(hrParams)) {
    message('hrParams is not provided, using defaults')
  }

  if (is.null(spPts)) {
    ade_id <- DT[, do.call(function(...) paste(..., sep = '-'), .SD),
                           .SDcols = c(splitBy)]

    spPts <- sf::as_Spatial(
      sf::st_as_sf(
        DT[, cbind(.SD, ade_id), .SDcols = c(coords)],
        coords = coords,
        crs = sf::st_crs(crs)
      ),
      IDs = ade_id
    )
  }

  hrParams$xy <- spPts

  if (hrType == 'mcp') {
    functionParams <- formals(adehabitatHR::mcp)
    if (all(names(hrParams) %in% names(functionParams))) {
      if (!('unout' %in% names(hrParams))) {
        hrParams$unout <- 'm2'
      }
      out <- do.call(adehabitatHR::mcp, hrParams)
    } else {
      stop(
        strwrap(
          prefix = " ",
          initial = "",
          x = 'hrParams provided do not match function parameters,
               see ?adehabitatHR::mcp'
        )
      )
    }
  } else if (hrType == 'kernel') {
    kernelParam <- formals(adehabitatHR::kernelUD)
    verticesParam <- formals(adehabitatHR::getverticeshr.estUD)

    if (all(names(hrParams) %in% c(names(kernelParam), names(verticesParam)))) {
      if (!('unout' %in% names(hrParams))) {
        hrParams$unout <- 'm2'
      }
      kern <- do.call(adehabitatHR::kernelUD,
                      hrParams[intersect(names(hrParams), names(kernelParam))])
      out <- do.call(adehabitatHR::getverticeshr,
                     c(x = list(kern),
                       hrParams[intersect(names(hrParams),
                                          names(verticesParam))]))
    } else {
      stop(
        strwrap(
          prefix = " ",
          initial = "",
          x = 'hrParams provided do not match
          function parameters, see ?adehabitatHR::kernelUD
          and ?adehabitatHR::getverticeshr'
        )
      )
    }
  } else {
    stop('hrType not one of "kernel" or "mcp"')
  }

  out_sf <- sf::st_as_sf(out)
  colnames(out_sf) <- gsub('id', id, colnames(out_sf))
  return(out_sf)
}


