lib/src/access.ex

defmodule Sorcery.Src.Access do
  alias Sorcery.Src.Utils
  @moduledoc """
  Functions to help implement Access for the Src struct.
  """


  def fetch(%{changes_db: ch, original_db: og, deletes: del}, k) do
    db = Sorcery.Utils.Maps.deep_merge(og, ch) |> Utils.remove_dels_from_db(del)
    Map.fetch(db, k)
  end


  def get_and_update(%{changes_db: ch} = src, k, cb) do
    case fetch(src, k) do
      {:ok, value} ->
        case cb.(value) do
          {current_value, new_value} ->
            new_changes = Map.put(ch, k, new_value)
            new_data    = Map.put(src, :changes_db, new_changes)
            {current_value, new_data}
          :pop ->
            new_data = Map.delete(src, k)
            {value, new_data}
        end
      :error ->
        case cb.(nil) do
          {current_value, new_value} ->
            new_changes = Map.put(ch, k, new_value)
            new_data    = Map.put(src, :changes_db, new_changes)
            {current_value, new_data}
          :pop ->
            new_data = Map.delete(src, k)
            {nil, new_data}
        end
    end
  end
  def get_and_update(src, k, cb) do
    value = Map.get(src, k)
    case cb.(value) do
      {current_value, new_value} -> 
        new_data = Map.put(src, k, new_value)
        {current_value, new_data}

      :pop ->
        new_data = Map.delete(src, k)
        {value, new_data}
    end
  end


  def pop(src, k) do
    value = Map.get(src, k)
    data = Map.delete(src, k)
    {value, data}
  end

  # Return a changes_db, excluding any keys that are in the original_db
  # We need for when get_and_update starts off with a fetch, which merges them.
  def diff(%{original_db: og, changes_db: ch}) do
    Enum.reduce(ch, %{}, fn {tk, table}, db_acc ->
      og_table = Map.get(og, tk)
      if og_table do
        new_table = Enum.reduce(table, %{}, fn {id, entity}, table_acc ->
          og_entity = Map.get(og_table, id)
          if og_entity do
            new_entity = Enum.reduce(entity, %{}, fn {k, v}, entity_acc ->
              og_v = Map.get(og_entity, k)
              if og_v do
                if og_v == v do
                  entity_acc
                else
                  Map.put(entity_acc, k, v)
                end
              else
                Map.put(entity_acc, k, v)
              end
            end)
            if Enum.empty?(new_entity) do
              table_acc
            else
              Map.put(table_acc, id, new_entity)
            end
          else
            Map.put(table_acc, id, entity)
          end
        end)
        if Enum.empty?(new_table) do
          db_acc
        else
          Map.put(db_acc, tk, new_table)
        end
      else
        Map.put(db_acc, tk, table)
      end
    end)
  end

end