#' Check whether a graph is directed
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `is.directed()` was renamed to [is_directed()] to create a more
#' consistent API.
#' @inheritParams is_directed
#' @keywords internal
#' @export
is.directed <- function(graph) {
  # nocov start
  lifecycle::deprecate_soft("2.0.0", "is.directed()", "is_directed()")
  is_directed(graph = graph)
} # nocov end

#' Delete vertices from a graph
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `delete.vertices()` was renamed to [delete_vertices()] to create a more
#' consistent API.
#' @inheritParams delete_vertices
#' @keywords internal
#' @export
delete.vertices <- function(graph, v) {
  # nocov start
  lifecycle::deprecate_soft("2.0.0", "delete.vertices()", "delete_vertices()")
  delete_vertices(graph = graph, v = v)
} # nocov end

#' Delete edges from a graph
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `delete.edges()` was renamed to [delete_edges()] to create a more
#' consistent API.
#' @inheritParams delete_edges
#' @keywords internal
#' @export
delete.edges <- function(graph, edges) {
  # nocov start
  lifecycle::deprecate_soft("2.0.0", "delete.edges()", "delete_edges()")
  delete_edges(graph = graph, edges = edges)
} # nocov end

#' Add vertices to a graph
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `add.vertices()` was renamed to [add_vertices()] to create a more
#' consistent API.
#' @inheritParams add_vertices
#' @keywords internal
#' @export
add.vertices <- function(graph, nv, ..., attr = list()) {
  # nocov start
  lifecycle::deprecate_soft("2.0.0", "add.vertices()", "add_vertices()")
  add_vertices(graph = graph, nv = nv, attr = attr, ...)
} # nocov end

#' Add edges to a graph
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `add.edges()` was renamed to [add_edges()] to create a more
#' consistent API.
#' @inheritParams add_edges
#' @keywords internal
#' @export
add.edges <- function(graph, edges, ..., attr = list()) {
  # nocov start
  lifecycle::deprecate_soft("2.0.0", "add.edges()", "add_edges()")
  add_edges(graph = graph, edges = edges, attr = attr, ...)
} # nocov end
#   IGraph R package
#   Copyright (C) 2005-2012  Gabor Csardi <csardi.gabor@gmail.com>
#   334 Harvard street, Cambridge, MA 02139 USA
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc.,  51 Franklin Street, Fifth Floor, Boston, MA
#   02110-1301 USA
#
###################################################################

###################################################################
# Structure building
###################################################################

#' Add edges to a graph
#'
#' The new edges are given as a vertex sequence, e.g. internal
#' numeric vertex ids, or vertex names. The first edge points from
#' `edges[1]` to `edges[2]`, the second from `edges[3]`
#' to `edges[4]`, etc.
#'
#' If attributes are supplied, and they are not present in the graph,
#' their values for the original edges of the graph are set to `NA`.
#'
#' @param graph The input graph
#' @param edges The edges to add, a vertex sequence with even number
#'   of vertices.
#' @param ... Additional arguments, they must be named,
#'   and they will be added as edge attributes, for the newly added
#'   edges. See also details below.
#' @param attr A named list, its elements will be added
#'   as edge attributes, for the newly added edges. See also details
#'   below.
#' @return The graph, with the edges (and attributes) added.
#'
#' @export
#'
#' @family functions for manipulating graph structure
#'
#' @examples
#' g <- make_empty_graph(n = 5) %>%
#'   add_edges(c(
#'     1, 2,
#'     2, 3,
#'     3, 4,
#'     4, 5
#'   )) %>%
#'   set_edge_attr("color", value = "red") %>%
#'   add_edges(c(5, 1), color = "green")
#' E(g)[[]]
#' plot(g)
add_edges <- function(graph, edges, ..., attr = list()) {
  ensure_igraph(graph)

  attrs <- list(...)
  attrs <- append(attrs, attr)
  nam <- names(attrs)
  if (length(attrs) != 0 && (is.null(nam) || any(nam == ""))) {
    cli::cli_abort("All attributes must be named.")
  }

  edges.orig <- ecount(graph)
  on.exit(.Call(Rx_igraph_finalizer))
  graph <- .Call(
    Rx_igraph_add_edges_manual,
    graph,
    as_igraph_vs(graph, edges) - 1
  )
  edges.new <- ecount(graph)

  if (edges.new - edges.orig != 0) {
    idx <- seq(edges.orig + 1, edges.new)
  } else {
    idx <- numeric()
  }

  for (i in seq(attrs)) {
    attr <- attrs[[nam[i]]]
    if (!is.null(attr)) {
      graph <- set_edge_attr(graph, nam[[i]], idx, attr)
    }
  }

  graph
}

#' Add vertices to a graph
#'
#' If attributes are supplied, and they are not present in the graph,
#' their values for the original vertices of the graph are set to
#' `NA`.
#'
#' @param graph The input graph.
#' @param nv The number of vertices to add.
#' @param ... Additional arguments, they must be named,
#'   and they will be added as vertex attributes, for the newly added
#'   vertices. See also details below.
#' @param attr A named list, its elements will be added
#'   as vertex attributes, for the newly added vertices. See also details
#'   below.
#' @return The graph, with the vertices (and attributes) added.
#'
#' @family functions for manipulating graph structure
#'
#' @export
#' @examples
#' g <- make_empty_graph() %>%
#'   add_vertices(3, color = "red") %>%
#'   add_vertices(2, color = "green") %>%
#'   add_edges(c(
#'     1, 2,
#'     2, 3,
#'     3, 4,
#'     4, 5
#'   ))
#' g
#' V(g)[[]]
#' plot(g)
add_vertices <- function(graph, nv, ..., attr = list()) {
  ensure_igraph(graph)

  attrs <- list(...)
  attrs <- append(attrs, attr)
  nam <- names(attrs)
  if (length(attrs) != 0 && (is.null(nam) || any(nam == ""))) {
    cli::cli_abort("All attributes must be named.")
  }

  vertices.orig <- vcount(graph)
  graph <- add_vertices_impl(
    graph = graph,
    nv = nv
  )
  vertices.new <- vcount(graph)

  if (vertices.new - vertices.orig != 0) {
    idx <- seq(vertices.orig + 1, vertices.new)
  } else {
    idx <- numeric()
  }

  for (i in seq(attrs)) {
    attr <- attrs[[nam[i]]]
    if (!is.null(attr)) {
      graph <- set_vertex_attr(graph, nam[[i]], idx, attr)
    }
  }

  graph
}

#' Delete edges from a graph
#'
#' @param graph The input graph.
#' @param edges The edges to remove, specified as an edge sequence. Typically
#'   this is either a numeric vector containing edge IDs, or a character vector
#'   containing the IDs or names of the source and target vertices, separated by
#'   `|`
#' @return The graph, with the edges removed.
#'
#' @family functions for manipulating graph structure
#'
#' @export
#' @examples
#' g <- make_ring(10) %>%
#'   delete_edges(seq(1, 9, by = 2))
#' g
#'
#' g <- make_ring(10) %>%
#'   delete_edges("10|1")
#' g
#'
#' g <- make_ring(5)
#' g <- delete_edges(g, get_edge_ids(g, c(1, 5, 4, 5)))
#' g
delete_edges <- function(graph, edges) {
  delete_edges_impl(
    graph = graph,
    edges = edges
  )
}

#' Delete vertices from a graph
#'
#' @param graph The input graph.
#' @param v The vertices to remove, a vertex sequence.
#' @return The graph, with the vertices removed.
#'
#' @family functions for manipulating graph structure
#'
#' @export
#' @examples
#' g <- make_ring(10) %>%
#'   set_vertex_attr("name", value = LETTERS[1:10])
#' g
#' V(g)
#'
#' g2 <- delete_vertices(g, c(1, 5)) %>%
#'   delete_vertices("B")
#' g2
#' V(g2)
delete_vertices <- function(graph, v) {
  delete_vertices_impl(
    graph = graph,
    vertices = v
  )
}

###################################################################
# Structure query
###################################################################

#' The size of the graph (number of edges)
#'
#' `ecount()` and `gsize()` are aliases.
#'
#' @param graph The graph.
#' @return Numeric scalar, the number of edges.
#'
#' @family structural queries
#'
#' @export
#' @examples
#' g <- sample_gnp(100, 2 / 100)
#' gsize(g)
#' ecount(g)
#'
#' # Number of edges in a G(n,p) graph
#' replicate(100, sample_gnp(10, 1 / 2), simplify = FALSE) %>%
#'   vapply(gsize, 0) %>%
#'   hist()
gsize <- function(graph) {
  ecount_impl(
    graph = graph
  )
}
#' @rdname gsize
#' @export
ecount <- gsize

#' Neighboring (adjacent) vertices in a graph
#'
#' A vertex is a neighbor of another one (in other words, the two
#' vertices are adjacent), if they are incident to the same edge.
#'
#' @param graph The input graph.
#' @param v The vertex of which the adjacent vertices are queried.
#' @param mode Whether to query outgoing (\sQuote{out}), incoming
#'   (\sQuote{in}) edges, or both types (\sQuote{all}). This is
#'   ignored for undirected graphs.
#' @return A vertex sequence containing the neighbors of the input vertex.
#'
#' @family structural queries
#'
#' @export
#' @examples
#' g <- make_graph("Zachary")
#' n1 <- neighbors(g, 1)
#' n34 <- neighbors(g, 34)
#' intersection(n1, n34)
neighbors <- function(graph, v, mode = c("out", "in", "all", "total")) {
  ensure_igraph(graph)
  mode <- igraph_match_arg(mode)

  v <- as_igraph_vs(graph, v)
  if (length(v) == 0) {
    cli::cli_abort("{.arg v} must specify at least one vertex.")
  }

  neighbors_impl(
    graph = graph,
    vid = v,
    mode = mode
  )
}

#' Incident edges of a vertex in a graph
#'
#' @param graph The input graph.
#' @param v The vertex of which the incident edges are queried.
#' @inheritParams neighbors
#' @return An edge sequence containing the incident edges of
#'   the input vertex.
#'
#' @family structural queries
#'
#' @export
#' @examples
#' g <- make_graph("Zachary")
#' incident(g, 1)
#' incident(g, 34)
incident <- function(graph, v, mode = c("all", "out", "in", "total")) {
  # For undirected graphs, mode doesn't matter, use "all"
  if (!is_directed(graph)) {
    mode <- "all"
  } else {
    mode <- igraph_match_arg(mode)
  }

  incident_impl(
    graph = graph,
    vid = v,
    mode = mode
  )
}

#' Check whether a graph is directed
#'
#' @param graph The input graph
#' @return Logical scalar, whether the graph is directed.
#'
#' @family structural queries
#'
#' @export
#' @examples
#' g <- make_ring(10)
#' is_directed(g)
#'
#' g2 <- make_ring(10, directed = TRUE)
#' is_directed(g2)
is_directed <- function(graph) {
  is_directed_impl(
    graph = graph
  )
}

#' Incident vertices of some graph edges
#'
#' @param graph The input graph
#' @param es The sequence of edges to query
#' @param names Whether to return vertex names or
#'   numeric vertex ids. By default vertex names are used.
#' @return A two column matrix of vertex names or vertex ids.
#'
#' @aliases get.edges
#' @family structural queries
#'
#' @export
#' @importFrom stats na.omit
#' @examples
#' g <- make_ring(5)
#' ends(g, E(g))
ends <- function(graph, es, names = TRUE) {
  ensure_igraph(graph)

  res <- matrix(NA_integer_, ncol = length(es), nrow = 2)

  if (length(es) > 0) {
    edge_data <- edges_impl(
      graph = graph,
      eids = es
    )
    res[, !is.na(es)] <- edge_data
  }

  if (names && is_named(graph)) {
    res <- vertex_attr(graph, "name")[res]
  }

  matrix(res, ncol = 2, byrow = TRUE)
}

#' @export
get.edges <- function(graph, es) {
  ends(graph, es, names = FALSE)
}

el_to_vec <- function(x, call = rlang::caller_env()) {
  if (is.data.frame(x)) {
    if (typeof(x[[1]]) == typeof(x[[2]])) {
      c(rbind(x[[1]], x[[2]]))
    } else {
      cli::cli_abort(
        "The columns of the data.frame are of different type ({typeof(x[[1]])} and {typeof(x[[2]])}) "
      )
    }
  } else if (inherits(x, "matrix")) {
    dimx <- dim(x)
    nrow <- dimx[[1]]
    ncol <- dimx[[2]]
    if (nrow == 2 && ncol == 2) {
      lifecycle::deprecate_stop(
        "2.1.5",
        "get_edge_ids(vp = 'is not allowed to be a 2 times 2 matrix')"
      )
    } else if (nrow == 2) {
      lifecycle::deprecate_warn(
        "2.1.5",
        "get_edge_ids(vp = 'supplied as a matrix should be a n times 2 matrix, not 2 times n')",
        details = "either transpose the matrix with t() or convert it to a data.frame with two columns."
      )
      c(x)
    } else if (ncol == 2) {
      c(t(x))
    } else {
      cli::cli_abort(
        "{.args vp} was supplied as a {dimx[1]} times {dimx[2]} matrix. Only n times 2 matrices are allowed"
      )
    }
  } else if (is.vector(x)) {
    x
  } else {
    cli::cli_abort(
      "Only two-column data.frames and matrices, and vectors are allowed for {.args vp}",
      call = call
    )
  }
}


#' Find the edge ids based on the incident vertices of the edges
#'
#' Find the edges in an igraph graph that have the specified end points. This
#' function handles multi-graph (graphs with multiple edges) and can consider
#' or ignore the edge directions in directed graphs.
#'
#' igraph vertex ids are natural numbers, starting from one, up to the number
#' of vertices in the graph. Similarly, edges are also numbered from one, up to
#' the number of edges.
#'
#' This function allows finding the edges of the graph, via their incident
#' vertices.
#'
#' @param graph The input graph.
#' @param vp The incident vertices, given as a two-column data frame, two-column matrix,
#'   or vector of vertex ids or symbolic vertex names.
#'   For a vector, the values are interpreted pairwise, i.e. the first and second are used for
#'   the first edge, the third and fourth for the second, etc.
#' @param directed Logical scalar, whether to consider edge directions in
#'   directed graphs. This argument is ignored for undirected graphs.
#' @param error Logical scalar, whether to report an error if an edge is not
#'   found in the graph. If `FALSE`, then no error is reported, and zero is
#'   returned for the non-existant edge(s).
#' @return A numeric vector of edge ids, one for each pair of input vertices.
#'   If there is no edge in the input graph for a given pair of vertices, then
#'   zero is reported. (If the `error` argument is `FALSE`.)
#' @author Gabor Csardi \email{csardi.gabor@@gmail.com}
#' @export
#' @family structural queries
#'
#' @examples
#'
#' g <- make_ring(10)
#' ei <- get_edge_ids(g, c(1, 2, 4, 5))
#' E(g)[ei]
#'
#' ## non-existant edge
#' get_edge_ids(g, c(2, 1, 1, 4, 5, 4))
#'
#' ## For multiple edges, a single edge id is returned,
#' ## as many times as corresponding pairs in the vertex series.
#' g <- make_graph(rep(c(1, 2), 5))
#' eis <- get_edge_ids(g, c(1, 2, 1, 2))
#' eis
#' E(g)[eis]
#'
get_edge_ids <- function(graph, vp, directed = TRUE, error = FALSE) {
  ensure_igraph(graph)

  vp <- el_to_vec(vp, call = rlang::caller_env())

  with_igraph_opt(
    list(return.vs.es = FALSE),
    get_eids_impl(
      graph,
      as_igraph_vs(graph, vp),
      directed,
      error
    )
  )
}

#' Find the edge ids based on the incident vertices of the edges
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `get.edge.ids()` was renamed to [get_edge_ids()] to create a more
#' consistent API.
#' @inheritParams get_edge_ids
#' @param multi
#'   `r lifecycle::badge("deprecated")`
#' @keywords internal
#' @export
get.edge.ids <- function(
  graph,
  vp,
  directed = TRUE,
  error = FALSE,
  multi = deprecated()
) {
  if (lifecycle::is_present(multi)) {
    if (isTRUE(multi)) {
      lifecycle::deprecate_stop("2.0.0", "get.edge.ids(multi = )")
    }

    lifecycle::deprecate_soft("2.0.0", "get.edge.ids(multi = )")
  }
  lifecycle::deprecate_soft("2.1.0", "get.edge.ids()", "get_edge_ids()")
  get_edge_ids(graph = graph, vp = vp, directed = directed, error = error)
}

#' Order (number of vertices) of a graph
#'
#' @description `vcount()` and `gorder()` are aliases.
#'
#' @param graph The graph
#' @return Number of vertices, numeric scalar.
#'
#' @family structural queries
#'
#' @export
#' @name gorder
#' @examples
#' g <- make_ring(10)
#' gorder(g)
#' vcount(g)
vcount <- function(graph) {
  as.numeric(vcount_impl(
    graph = graph
  ))
}

#' @export
#' @rdname gorder
gorder <- vcount

#' Adjacent vertices of multiple vertices in a graph
#'
#' This function is similar to [neighbors()], but it queries
#' the adjacent vertices for multiple vertices at once.
#'
#' @param graph Input graph.
#' @param v The vertices to query.
#' @inheritParams neighbors
#' @return A list of vertex sequences.
#'
#' @family structural queries
#' @export
#' @examples
#' g <- make_graph("Zachary")
#' adjacent_vertices(g, c(1, 34))
adjacent_vertices <- function(graph, v, mode = c("out", "in", "all", "total")) {
  ensure_igraph(graph)

  vv <- as_igraph_vs(graph, v) - 1
  mode <- switch(match.arg(mode), "out" = 1, "in" = 2, "all" = 3, "total" = 3)

  on.exit(.Call(Rx_igraph_finalizer))

  res <- .Call(Rx_igraph_adjacent_vertices, graph, vv, mode)
  res <- lapply(res, `+`, 1)

  if (igraph_opt("return.vs.es")) {
    res <- lapply(res, unsafe_create_vs, graph = graph, verts = V(graph))
  }

  if (is_named(graph)) {
    names(res) <- V(graph)$name[vv + 1]
  }

  res
}

#' Incident edges of multiple vertices in a graph
#'
#' This function is similar to [incident()], but it
#' queries multiple vertices at once.
#'
#' @param graph Input graph.
#' @param v The vertices to query
#' @inheritParams neighbors
#' @return A list of edge sequences.
#'
#' @family structural queries
#' @export
#' @examples
#' g <- make_graph("Zachary")
#' incident_edges(g, c(1, 34))
incident_edges <- function(graph, v, mode = c("out", "in", "all", "total")) {
  ensure_igraph(graph)

  vv <- as_igraph_vs(graph, v) - 1
  mode <- switch(match.arg(mode), "out" = 1, "in" = 2, "all" = 3, "total" = 3)

  on.exit(.Call(Rx_igraph_finalizer))

  res <- .Call(Rx_igraph_incident_edges, graph, vv, mode)
  res <- lapply(res, `+`, 1)

  if (igraph_opt("return.vs.es")) {
    res <- lapply(res, unsafe_create_es, graph = graph, es = E(graph))
  }

  if (is_named(graph)) {
    names(res) <- V(graph)$name[vv + 1]
  }

  res
}

#' Invalidate the cache of a graph
#'
#' igraph graphs cache some basic properties (such as whether the graph is a
#' DAG or whether it is simple) in an internal data structure for faster
#' repeated queries. This function invalidates the cache, forcing a
#' recalculation of the cached properties the next time they are needed.
#'
#' You should not need to call this function during normal usage; however, it
#' may be useful for debugging cache-related issues. A tell-tale sign of an
#' invalid cache entry is when the result of a cached function (such as
#' \code{\link{is_dag}()} or \code{\link{is_simple}()}) changes after calling
#' this function.
#'
#' @param graph The graph whose cache is to be invalidated.
#' @return The graph with its cache invalidated. Since the graph is modified
#'   in place in R as well, you can also ignore the return value.
#'
#' @family low-level operations
#'
#' @export
#' @examples
#' g <- make_ring(10)
#' # Cache is populated when calling is_simple()
#' is_simple(g)
#' # Invalidate cache (for debugging purposes)
#' invalidate_cache(g)
#' # Result should be the same
#' is_simple(g)
invalidate_cache <- function(graph) {
  invalidate_cache_impl(
    graph = graph
  )
}
