defmodule Tensor.Tensor.Helper do

  # LISTS

  @doc """
  Swaps the element at position `pos_a` with the element at position `pos_b` inside a list.


  # Examples

    iex> swap_elems_in_list([1,2,3,4,5], 1, 3)
    [1, 4, 3, 2, 5]
  """
  def swap_elems_in_list(list, pos_a, pos_a), do: list 
  def swap_elems_in_list(list, pos_a, pos_b) when pos_a < pos_b do
    {initial, rest} = Enum.split(list, pos_a)
    {between, tail} = Enum.split(rest, pos_b - pos_a)
    a = hd(between)
    b = hd(tail)
    initial ++ [b] ++ tl(between) ++ [a] ++ tl(tail)
  end

  def swap_elems_in_list(list, pos_a, pos_b) when pos_b < pos_a, do: swap_elems_in_list(list, pos_b, pos_a)


  # MAPS

  @doc """
  Puts `val` under `map` inside a nested map indicated with `keys`.
  This is required, as the normal `put_in` will fail if one of the levels
  indicated by `keys` is not initialized to a map yet.


  ## Examples:

    iex>put_in_path(%{}, [1,2,3], 4)
    %{1 => %{2 => %{3 => 4}}}
  """
  def put_in_path(map, keys, val) do
    nestable_keys = keys |> Enum.map(&Access.key(&1, %{}))
    put_in(map, nestable_keys, val)
  end
  # def put_in_path(map, keys, val) do
  #   do_put_in_path(map, keys, val, [])
  # end

  # defp do_put_in_path(map, [key], val, acc) do
  #   new_acc = acc ++ [key]
  #   put_in(map, new_acc, val)
  # end

  # defp do_put_in_path(map, [key | keys], val, acc) do
  #   new_acc = acc ++ [key]
  #   new_map = put_in(map, new_acc, get_in(map, new_acc) || %{})
  #   do_put_in_path(new_map, keys, val, new_acc)
  # end

  @doc """
  Returns the keywise difference of two maps.
  So: Only the part of `map_a` is returned that has keys not in `map_b`.

  ## Examples:

      iex> Tensor.Helper.map_difference(%{a: 1, b: 2, c: 3, d: 4}, %{b: 3, d: 5})
      %{a: 1, c: 3}

  """
  def map_difference(map_a, map_b) do
    Map.keys(map_b)
    |> Enum.reduce(map_a, fn key, map ->
      {_, new_map} = Map.pop(map, key)
      new_map
    end)
  end

  @doc """
  Returns the keywise difference of two maps.
  So: Only the part of `map_a` is returned that has keys also in `map_b`.

  ## Examples:

      iex> Tensor.Helper.map_intersection(%{a: 1, b: 2, c: 3, d: 4}, %{b: 3, d: 5})
      %{b: 2, d: 4}

  """
  def map_intersection(map_a, map_b) do
    diff = map_difference(map_a, map_b)
    map_difference(map_a, diff)
  end

  @doc false
  defmacro use_if_exists?(module, opts) do
    module = Macro.expand(module, __ENV__)
    if Code.ensure_loaded?(module) do
      quote do
        use unquote(module), unquote(opts)
      end
    end
  end

end

