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