convert.to.subnet <- function(x, mask) {
  result <- intToBits(IP::ipv4(x)@ipv4)
  result <- matrix(result, nrow = 32)
  result[seq_len(32 - mask), ] <- raw(1)
  result <- packBits(c(result), "integer")
  result <- suppressWarnings(as.character(IP::ipv4(result)))
  # Gets a warning about negative values because the actual value is an unsigned integer
  result
}


in.malicious.ips <- function(x, malicious.ips) {
  # x can have duplicated elements
  malicious.ips.singletons <- malicious.ips[ ! grepl("/", malicious.ips)]
  malicious.ips.ranges <- malicious.ips[grepl("/", malicious.ips)]
  result <- x %in% malicious.ips.singletons
  result <- result | (! is.na(IP::ip.match(IP::ipv4(x), IP::ipv4r(malicious.ips.ranges))))
  result
}


#' Collect connected peers' IP addresses
#'
#' @description Collects IP addreses of peers that the local node has
#' established outbound connections to. The time and set of IP addreses are
#' saved to a CSV file. These IP addreses are checked against an optional
#' set of suspected malicious IP addresses. Information about the share of
#' outbound connections to suspected maclicious IP addreses is printed.
#' IP addresses are grouped by subnet and information is printed to check for
#' possible "subnet saturation" by malicious entities. This function is an
#' infinite loop. `ctrl + c` to interrupt the function.
#'
#' @param csv.file The name of the CSV file to write to and read from. If it
#' already exists, data will be appended to it and the whole file will be
#' used to compute top subnet information.
#' @param unrestricted.rpc.url URL and port of the `monerod` unrestricted RPC.
#' Default is `http://127.0.0.1:18081`
#' @param malicious.ips A character vector of IP addreses that are suspected
#' to be malicious.
#' @param top.subnet.mask Numeric value. The IP address subnet mask to print
#' summary information about.
#' @param n.top.subnets Number of subnets to print summary information about.
#' @param poll.time How often, in seconds, to collect data from the local
#' monero node. Default is 30 seconds.
#'
#' @return
#' NULL (invisible)
#' @export
#'
#' @examples
#' \dontrun{
#' suspected.malicious.ips <-readLines(
#'   "https://raw.githubusercontent.com/Boog900/monero-ban-list/refs/heads/main/ban_list.txt")
#' peers.ip.collect(malicious.ips = suspected.malicious.ips)
#' }

peers.ip.collect <- function(csv.file = "xmr-peers-ip.csv",
  unrestricted.rpc.url = "http://127.0.0.1:18081", malicious.ips = NULL,
  top.subnet.mask = 24, n.top.subnets = 10, poll.time = 30) {

  if ( ! (is.null(malicious.ips) | is.vector(malicious.ips)) ) {
    stop("malicious.ips must be an atomic vector, i.e. not a data.frame or matrix.")
  }

  if (file.exists(csv.file)) {
    peer.ip.data <- as.matrix(read.csv(csv.file, header = FALSE))
    peer.ip.data <- peer.ip.data[, -1] # Remove the time column
  } else {
    file.create(csv.file)
    peer.ip.data <- matrix(NA_character_, ncol = 24, nrow = 0)
  }

  json.post <- RJSONIO::toJSON(
    list(
      jsonrpc = "2.0",
      id = "0",
      method = "get_connections",
      params = ""
    )
  )

  session.start.time <- Sys.time()

  cat(paste0(format(session.start.time, "%Y-%m-%d %T"),
    ifelse(length(malicious.ips) > 0, " Start collecting peer data...\n",
      " Start collecting peer data (no malicious peer list)...\n")))

  # peer.malicious.ips.data <- matrix(NA_real_, ncol = 24, nrow = 0)

  while (TRUE) {

    RPC.time <- as.numeric(Sys.time())

    peers <- RJSONIO::fromJSON(
      RCurl::postForm(paste0(unrestricted.rpc.url, "/json_rpc"),
        .opts = list(
          userpwd = "",
          postfields = json.post,
          httpheader = c('Content-Type' = 'application/json', Accept = 'application/json')
        )
      ), asText = TRUE
    )

    if(length(peers$result$connections) == 0 ) {
      cat("Unexpected results from node's RPC response. Check that monerod's unrestricted RPC port is at ",
        unrestricted.rpc.url, "\n")
      Sys.sleep(poll.time)
      next
    }

    peers <- peers$result$connections

    peer.address <- sapply(peers, FUN = function(x) {gsub("[:][0-9]*", "", x$address)})
    peer.incoming <- sapply(peers, FUN = function(x) {x$incoming})

    peer.address <- peer.address[ ! peer.incoming]

    peer.address.ipv4 <- IP::ipv4(peer.address)

    peer.address <- peer.address[order(peer.address.ipv4)]

    peer.ip.data <- rbind(peer.ip.data,
      matrix(c(peer.address, rep(NA_character_, 24 - length(peer.address))),
        ncol = 24, nrow = 1))

    write.table(cbind(RPC.time, peer.ip.data[nrow(peer.ip.data), , drop = FALSE]), file = csv.file,
      sep = ",", append = TRUE, col.names = FALSE, row.names = FALSE)

    if (length(malicious.ips) > 0) {

      peer.address.malicious.ips <- as.numeric(in.malicious.ips(peer.address, malicious.ips))

      # peer.malicious.ips.data <- rbind(peer.malicious.ips.data,
      #   matrix(c(peer.address.malicious.ips, rep(NA_real_, 24 - length(peer.address.malicious.ips))),
      #     ncol = 24, nrow = 1))

      cat(format(Sys.time(), "%Y-%m-%d %T"), " Outbound peers on malicious IPs list: ",
        sum(peer.address.malicious.ips), "/", length(peer.address.malicious.ips),
        " (", round(100 * mean(peer.address.malicious.ips)), "%)", "\n", sep = "")

    }

    peer.ip.data.unique <- na.omit(unique(c(peer.ip.data)))

    peer.ip.data.unique.subnet <- convert.to.subnet(peer.ip.data.unique, top.subnet.mask)

    if (length(peer.ip.data.unique.subnet) < n.top.subnets) { Sys.sleep(poll.time); next }

    subnets.message <- sort(table(peer.ip.data.unique.subnet), decreasing = TRUE)[1:n.top.subnets]

    subnets.message <- paste0(names(subnets.message), "/", top.subnet.mask,
      ": ", subnets.message, collapse = ", ")

    cat("Top subnets (", 2^(32 - top.subnet.mask) - 2,
      " possible /", top.subnet.mask, " IPs each): ", subnets.message,
      "\n", sep = "")

    Sys.sleep(poll.time)

  }

  return(invisible(NULL))

}