lib/acid_helper.ex

defmodule Bio.AcidHelper do
  # Helpers for things that are similar between DNA and RNA modules
  # Please don't use this unless you're me.
  @moduledoc """
  Internal helper module for dealing with amino acids
  """

  @type alphabet :: String.t()
  @type index :: integer()
  @type character :: String.t()
  @type mismatch :: [{:mismatch_alpha, character, index, alphabet}]

  @doc false
  def complement(alpha_mod, acid_mod, %_{alphabet: alpha} = sequence, opts) do
    alphabet = get_alpha({alpha, Keyword.get(opts, :alphabet)}, alpha_mod)

    comps =
      sequence
      |> Enum.with_index()
      |> Enum.map(fn {base, index} ->
        {apply(alpha_mod, :complement, [base, alphabet]), index}
      end)

    cond do
      Enum.any?(comps, fn {{status, _}, _} -> status == :error end) ->
        {:error,
         Enum.reduce(comps, [], fn {{status, result}, index}, acc ->
           case status do
             :ok ->
               acc

             :error ->
               {_, char, alpha} = result
               List.insert_at(acc, -1, {:mismatch_alpha, char, index, alpha})
           end
         end)}

      true ->
        {:ok,
         Enum.reduce(comps, "", fn {{_, result}, _}, acc ->
           acc <> result
         end)
         |> then(&apply(acid_mod, :new, [&1, [{:alphabet, alphabet}]]))}
    end
  end

  @doc false
  def complement(alpha_mod, sequence, opts) when is_binary(sequence) do
    alphabet = get_alpha({nil, Keyword.get(opts, :alphabet)}, alpha_mod)

    comps =
      sequence
      |> String.graphemes()
      |> Enum.with_index()
      |> Enum.map(fn {base, index} ->
        {apply(alpha_mod, :complement, [base, alphabet]), index}
      end)

    cond do
      Enum.any?(comps, fn {{status, _}, _} -> status == :error end) ->
        {:error,
         Enum.reduce(comps, [], fn {{status, result}, index}, acc ->
           case status do
             :ok ->
               acc

             :error ->
               {_, char, alpha} = result
               List.insert_at(acc, -1, {:mismatch_alpha, char, index, alpha})
           end
         end)}

      true ->
        {:ok,
         Enum.reduce(comps, "", fn {{_, result}, _}, acc ->
           acc <> result
         end)}
    end
  end

  @doc false
  defp get_alpha(opts, alpha_mod) do
    case opts do
      {nil, nil} -> apply(alpha_mod, :common, [])
      {nil, opted} -> opted
      {built, nil} -> built
      {_built, opted} -> opted
    end
  end
end