# Load necessary libraries
pacman::p_load(pacman, websocket, jsonlite, later, logging, lubridate, ggplot2, maps, gridExtra)

# Initialise earthquake data
quake_data <- data.frame(
  mag = numeric(),
  location = character(),
  timestamp = character(),
  longitude = numeric(),
  latitude = numeric(),
  depth = numeric(),
  stringsAsFactors = FALSE
)

# Function to update the map and table
update_map_and_table <- function(data) {
  # Plot world map
  world_map <- map_data("world")
  p1 <- ggplot() +
    geom_polygon(data = world_map, aes(x = long, y = lat, group = group), fill = "#cccccc", color = "#000000") +
    geom_point(data = data, aes(x = longitude, y = latitude, color = mag, size = mag), alpha = 0.7) +
    scale_color_gradient(low = "#ffff99", high = "#cc0033") +
    scale_size(range = c(2, 5)) +
    labs(title = "Earthquakes around the World", x = "Longitude", y = "Latitude") +
    theme_minimal()
  
  # Create the table of earthquakes with depth information
  quake_table <- data[, c("location", "mag", "timestamp", "depth")]
  table_grob <- tableGrob(quake_table, rows = NULL)
  
  # Plot both the map and the table side by side
  grid.arrange(p1, table_grob, ncol = 2, widths = c(3/5, 2/5))
}

# Set up WebSocket connection
ws <- WebSocket$new("wss://www.seismicportal.eu/standing_order/websocket")

# WebSocket message handler
ws$onMessage(function(event) {
  message <- event$data
  data <- fromJSON(message)
  
  # Print the structure of incoming data (for debugging purposes)
  print_data_structure(data)
  
  # Extract relevant earthquake data, with safety checks
  mag <- ifelse(!is.null(data$data$properties$mag), data$data$properties$mag, NA)
  location <- ifelse(!is.null(data$data$properties$flynn_region), data$data$properties$flynn_region, "Unknown")
  time <- ifelse(!is.null(data$data$properties$time), data$data$properties$time, NA)
  timestamp <- if (!is.na(time)) {
    as.character(lubridate::ymd_hms(time, tz = "UTC"))
  } else {
    "NA"
  }
  longitude <- ifelse(!is.null(data$data$geometry$coordinates) && length(data$data$geometry$coordinates) >= 1, data$data$geometry$coordinates[1], NA)
  latitude <- ifelse(!is.null(data$data$geometry$coordinates) && length(data$data$geometry$coordinates) >= 2, data$data$geometry$coordinates[2], NA)
  
  # Extract depth
  depth <- ifelse(!is.null(data$data$properties$depth), data$data$properties$depth, NA)
  
  # Create a new data frame for this quake event
  new_quake_data <- data.frame(
    mag = mag, 
    location = location, 
    timestamp = timestamp, 
    longitude = longitude, 
    latitude = latitude,
    depth = depth,
    stringsAsFactors = FALSE
  )
  
  # Ensure the column names and types match those of quake_data
  if (all(colnames(new_quake_data) == colnames(quake_data))) {
    # Add new data to quake_data
    quake_data <<- rbind(quake_data, new_quake_data)
    
    # Update map and table
    update_map_and_table(quake_data)
  } else {
    cat("Error: Incoming data does not match the structure of quake_data\n")
  }
})

ws$onOpen(function(event) {
  cat("WebSocket connection opened\n")
})

ws$onClose(function(event) {
  cat("WebSocket connection closed\n")
})

ws$onError(function(event) {
  cat("WebSocket error: ", event$message, "\n")
})

# Function to print the structure of incoming data
print_data_structure <- function(data) {
  cat("Structure of incoming data:\n")
  print(str(data))
}