defmodule SimpleGraph do
@moduledoc """
A complete graph
"""
alias SimpleGraph.Node
use Agent
require Logger
@type t :: %__MODULE__{nodes: %{binary() => Node.t()}, id: String.t(), name: String.t()}
@enforce_keys [:id, :name]
defstruct nodes: %{}, id: "", name: ""
def start_link(graph_name: name) when is_atom(name) do
Agent.start_link(fn -> %SimpleGraph{id: UUID.uuid4(), name: name} end, name: name)
end
@spec graph(atom()) :: SimpleGraph.t()
def graph(graph_name) when is_atom(graph_name) do
Agent.get(graph_name, fn %SimpleGraph{} = state -> state end)
end
@spec add_node(
graph: SimpleGraph.t(),
name: atom(),
node: binary(),
outgoing: binary(),
incoming: binary()
) ::
SimpleGraph.t()
def add_node(name: name, node: node) when is_atom(name) do
Agent.update(name, fn %SimpleGraph{} = graph ->
%{graph | nodes: Map.put(graph.nodes, node.id, node)}
end)
end
def add_node(name: name, node: node_id, outgoing: outgoing_id)
when is_atom(name) and is_binary(node_id) and is_binary(outgoing_id) do
Logger.debug("Adding node #{name}, with first node #{node_id} and outgoging #{outgoing_id}")
Agent.update(name, fn %SimpleGraph{} = graph ->
add_node(graph: graph, node: node_id, outgoing: outgoing_id)
end)
end
def add_node(name: name, node: node_id, incoming: incoming_id)
when is_atom(name) and is_binary(node_id) and is_binary(incoming_id) do
Logger.debug("Adding node #{name}, with first node #{node_id} and incoming #{incoming_id}")
Agent.update(name, fn %SimpleGraph{} = graph ->
add_node(graph: graph, node: node_id, incoming: incoming_id)
end)
end
def add_node(graph: %SimpleGraph{} = graph, node: node_id, incoming: incoming_id)
when is_binary(node_id) and is_binary(incoming_id) do
with {:ok, node} <- get_node(graph, node_id),
{:ok, incoming} <- get_node(graph, incoming_id) do
[{:self, new_node}, {:incoming, new_incoming}] =
Node.add_node(self: node, incoming: incoming)
put_node(graph, new_node)
|> put_node(new_incoming)
end
end
def add_node(graph: %SimpleGraph{} = graph, node: node_id, outgoing: outgoing_id)
when is_binary(node_id) and is_binary(outgoing_id) do
with {:ok, node} <- get_node(graph, node_id),
{:ok, outgoing} <- get_node(graph, outgoing_id) do
[self: new_node, outgoing: new_outgoing] = Node.add_node(self: node, outgoing: outgoing)
put_node(graph, new_node)
|> put_node(new_outgoing)
else
{:error, reason} ->
Logger.warning(reason, graph_id: graph.id)
graph
end
end
@spec get_node(SimpleGraph.t(), String.t()) :: {:ok, Node.t()} | {:error, String.t()}
def get_node(%SimpleGraph{} = graph, node_id) do
case Map.fetch(graph.nodes, node_id) do
{:ok, %Node{} = node} -> {:ok, node}
:error -> {:error, "Node #{node_id} not found"}
end
end
@spec put_node(SimpleGraph.t(), Node.t()) :: SimpleGraph.t()
def put_node(%SimpleGraph{} = graph, %Node{} = node) do
%{graph | nodes: Map.put(graph.nodes, node.id, node)}
end
end