defmodule Outlook.InternalTree.TunitModifications do

  alias Outlook.InternalTree.{InternalNode,TranslationUnit}

  def modifiers do
    [
      %{
        name: "unite_with_next",
        fn: &unite_with_next/2,
        label: "Unite with next",
        description: "unite selected translation unit with (unselected) next"
      },
      # %{
      #   name: "split_tunit",
      #   fn: &split_tunit/2,
      #   label: "Split Translation unit",
      #   description: "split translation unit into two"
      # }
    ]
  end

  # Modifier functions

  defp unite_with_next(nodelist, tu_ids) when is_list(tu_ids) do
    ids_to_process = Enum.reverse(tu_ids)
    Enum.reduce(ids_to_process, nodelist, fn id, nodes -> unite_with_next(nodes, id) end)
  end
  defp unite_with_next(nodelist, tu_id) do
    ind = Enum.find_index(nodelist, fn n -> n.nid == tu_id end)
    nodes = Enum.slice(nodelist, ind, 2)
    unite_with_next(nodelist, ind, nodes)
  end
  defp unite_with_next(nodelist, ind, [unit,next]) do
    nunit = %TranslationUnit{unit | content: unit.content <> next.content}
    nodelist
    |> List.replace_at(ind, nunit)
    |> List.delete_at(ind + 1)
  end
  defp unite_with_next(nodelist, _, [_]) do
    nodelist
  end

  defp split_tunit(_nodelist, _tu_ids) do
  end

  # Function and helpers to apply modifiers

  def apply_modifier([ %InternalNode{} = node | rest ], modifier, tu_ids) when node.type == :element do
    content = case List.first(node.content) do
      %TranslationUnit{} -> process_tunit_list(node.content, modifier, tu_ids)
      _ -> apply_modifier(node.content, modifier, tu_ids)
    end
    [%InternalNode{node| content: content} | apply_modifier(rest, modifier, tu_ids)]
  end
  def apply_modifier([node, rest], modifier, tu_ids), do: [node | apply_modifier(rest, modifier, tu_ids)]
  def apply_modifier([],_, _), do: []

  def process_tunit_list(tunits, modifier, tu_ids) do
    modi_fun = get_modi_fun(modifier)
    ids_to_process = get_ids_to_process(tunits, tu_ids)
    case length(ids_to_process) do
      0 -> tunits
      _ -> modi_fun.(tunits, ids_to_process)
    end
  end

  @doc false
  def get_ids_to_process(tunits, tu_ids) do
    present_ids = Enum.map(tunits, fn u -> u.nid end)
    found_ids = MapSet.new(present_ids)
    |> MapSet.intersection(MapSet.new(tu_ids))
    |> MapSet.to_list()
    # make sure to return ids in the order they occur in tunits
    Enum.filter(present_ids, fn pres -> pres in found_ids end)
  end

  defp get_modi_fun(modifier) do
    modifiers()
    |> Enum.find(fn m -> m.name == modifier end)
    |> Map.get(:fn)
  end
end
