lib/zipper_ex.ex

defmodule ZipperEx do
  @moduledoc ~S"""
  An Elixir implementation for Zipper based on the paper
  [Functional pearl: the zipper](
  https://www.st.cs.uni-saarland.de/edu/seminare/2005/advanced-fp/docs/huet-zipper.pdf).
  by Gérard Huet.

  `ZipperEx` provides functions to handle an traverse tree data structures.

  ## Usage

  To create a zipper you can use the `ZipperEx.Zipable` protocol or create a
  module.

  ### Creating a zipper module

  Imagine we have a tree structure based on a tuple with an integer value and a
  list of cildren. The leafs of the tree are also integers. A tree may then look
  like the following:

  ```elixir
  {1, [2, {3, [4, 5]}, 6, {7, [0]}]}
  #   1
  # +-+-+---+-+
  # 2   3   6 7
  #   +-+-+   +
  #   4   5   0
  ```

  To handle this tree we create the following module:
  ```elixir
  defmodule Support.Zipper do
    use ZipperEx

    @impl ZipperEx
    def branch?(item) do
      case item do
        {_, [_ | _]} -> true
        _else -> false
      end
    end

    @impl ZipperEx
    def children({_, children}), do: children

    @impl ZipperEx
    def make_node({value, _children}, children), do: {value, children}

    def make_node(value, children), do: {value, children}
  end
  ```

  For `use ZipperEx` we have to implement the callbacks `c:branch?/1`,
  `c:children/1` and `c:make_node/2`.

  The `Support.Zipper` module contains all functions the `ZipperEx` also
  provides.

  We can now use `Support.Zipper`.

      iex> alias Support.Zipper
      iex> zipper = Zipper.new({1, [2, {3, [4, 5]}, 6, {7, [0]}]})
      #ZipperEx<{1, [2, {3, [4, 5]}, 6, {7, [0]}]}>
      iex> zipper = Zipper.down(zipper)
      #ZipperEx<2>
      iex> zipper = Zipper.right(zipper)
      #ZipperEx<{3, [4, 5]}>
      iex> Zipper.remove(zipper) |> Zipper.root()
      {1, [2, 6, {7, [0]}]}
      iex> {_zipper, acc} = zipper |> ZipperEx.top() |> ZipperEx.traverse([], fn
      ...>   %{loc: {_value, _children}} = zipper, acc -> {zipper, acc}
      ...>   %{loc: value} = zipper, acc -> {zipper, [value | acc]}
      ...> end)
      iex> acc
      [0, 6, 5, 4, 2]

  ### Using the `ZipperEx.Zipable` protocol

  Similar to the module example the protocol needs implementations for
  `ZipperEx.Zipable.branch?/1`, `ZipperEx.Zipable.children/1` and
  `ZipperEx.Zipable.make_node/2`.

  ```elixir
  defmodule Support.TreeNode do
    @moduledoc false

    defstruct value: nil, children: []

    def new({value, children}) do
      struct!(__MODULE__, value: value, children: Enum.map(children, &new/1))
    end

    def new(value), do: struct!(__MODULE__, value: value)

    defimpl ZipperEx.Zipable do
      def branch?(%{children: [_ | _]}), do: true

      def branch?(_node), do: false

      def children(%{children: children}), do: children

      def make_node(node, children), do: %{node | children: children}
    end

    defimpl Inspect do
      def inspect(node, _opts), do: "#TreeNode<#{node.value}, #{inspect(node.children)}>"
    end
  end
  ```

  `Support.TreeNode` can then be used as follows:

        iex> alias Support.TreeNode
        iex> tree = TreeNode.new({1, [2, {3, [4, 5]}]})
        iex> zipper = ZipperEx.new(tree)
        iex> ZipperEx.down(zipper)
        #ZipperEx<#TreeNode<2, []>>
        iex> zipper |> ZipperEx.down() |> ZipperEx.rightmost()
        #ZipperEx<#TreeNode<3, [#TreeNode<4, []>, #TreeNode<5, []>]>>
        iex> {_zipper, acc} = ZipperEx.traverse(zipper, [], fn z, acc ->
        ...>   node = ZipperEx.node(z)
        ...>   {z, [node.value | acc]}
        ...> end)
        iex> acc
        [5, 4, 3, 2, 1]

  ## Example supporters

  The module `Support.Zipper` and `Support.TreeNode` are also used in the
  examples for this module.
  """

  import Kernel, except: [node: 1]

  alias ZipperEx.Zipable

  @type tree :: term()

  @typedoc """
  The `ZipperEx` type:
  * `loc`: The current location of the zipper.
  * `left`: The left side.
  * `left`: The right side.
  * `path`: The path to the top.
  * `module`: The zipper module.
  """
  @type t(tree) :: %ZipperEx{
          loc: tree,
          left: [tree],
          path: t(tree) | nil | :end,
          right: [tree],
          module: module() | nil
        }
  @type t :: t(term())

  @enforce_keys [:loc]
  defstruct left: [],
            path: nil,
            right: [],
            loc: nil,
            module: nil

  @doc """
  Should return `true` if the given `node` is a branch.
  """
  @callback branch?(node :: term()) :: boolean

  @doc """
  Returns the children of the given `node`.
  """
  @callback children(node :: term()) :: [term()]

  @doc """
  Creates a `node` from the given `node` and `children`.
  """
  @callback make_node(node :: term(), children :: [term()]) :: term()

  defmacro __using__(_opts) do
    quote do
      @behaviour ZipperEx

      def new(tree), do: struct!(ZipperEx, loc: tree, module: __MODULE__)

      def branch?(%ZipperEx{loc: loc}), do: branch?(loc)
      def children(%ZipperEx{loc: loc}), do: children(loc)
      def make_node(%ZipperEx{loc: loc}, children), do: make_node(loc, children)

      defdelegate append_child(zipper, child), to: ZipperEx
      defdelegate down(zipper), to: ZipperEx
      defdelegate end?(zipper), to: ZipperEx
      defdelegate find(zipper, fun), to: ZipperEx
      defdelegate insert_child(zipper, child), to: ZipperEx
      defdelegate insert_right(zipper, child), to: ZipperEx
      defdelegate insert_left(zipper, child), to: ZipperEx
      defdelegate left(zipper), to: ZipperEx
      defdelegate leftmost(zipper), to: ZipperEx
      defdelegate map(zipper, fun), to: ZipperEx
      defdelegate next(zipper), to: ZipperEx
      defdelegate node(zipper), to: ZipperEx
      defdelegate prev(zipper), to: ZipperEx
      defdelegate remove(zipper), to: ZipperEx
      defdelegate root(zipper), to: ZipperEx
      defdelegate replace(zipper, node), to: ZipperEx
      defdelegate right(zipper), to: ZipperEx
      defdelegate rightmost(zipper), to: ZipperEx
      defdelegate top(zipper), to: ZipperEx
      defdelegate traverse(zipper, fun), to: ZipperEx
      defdelegate traverse(zipper, acc, fun), to: ZipperEx
      defdelegate traverse_while(zipper, fun), to: ZipperEx
      defdelegate traverse_while(zipper, acc, fun), to: ZipperEx
      defdelegate up(zipper), to: ZipperEx
      defdelegate update(zipper, fun), to: ZipperEx
    end
  end

  @doc """
  Returns a new `%ZipperEx{}` from a given `tree` or `%ZipperEx`.

  ## Examples

      iex> alias Support.Zipper
      iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
      #ZipperEx<{1, [{2, [3, 4]}, 5]}>
      iex> zipper |> ZipperEx.next() |> ZipperEx.new()
      #ZipperEx<{2, [3, 4]}>
      iex> zipper |> Zipper.next() |> ZipperEx.new()
      #ZipperEx<{2, [3, 4]}>

      iex> alias Support.TreeNode
      iex> zipper = ZipperEx.new(TreeNode.new({1, [2]}))
      #ZipperEx<#TreeNode<1, [#TreeNode<2, []>]>>
      iex> zipper |> ZipperEx.next() |> ZipperEx.new()
      #ZipperEx<#TreeNode<2, []>>
  """
  @spec new(ZipperEx.t() | tree()) :: ZipperEx.t()
  def new(%ZipperEx{path: nil} = zipper), do: zipper

  def new(%ZipperEx{} = zipper), do: %ZipperEx{zipper | path: nil, left: [], right: []}

  def new(tree), do: struct!(ZipperEx, loc: tree)

  @doc """
  Returns `true` if the given `node` is a branch.
  """
  @spec branch?(ZipperEx.t()) :: boolean()
  def branch?(%ZipperEx{loc: loc, module: nil}), do: Zipable.branch?(loc)

  def branch?(%ZipperEx{} = zipper), do: Zipable.branch?(zipper)

  @doc """
  Returns the children of the given `node`.
  """
  @spec children(ZipperEx.t()) :: [tree()]
  def children(%ZipperEx{loc: loc, module: nil}), do: Zipable.children(loc)

  def children(%ZipperEx{} = zipper), do: Zipable.children(zipper)

  @doc """
  Creates a `node` from the given `node` and `children`.
  """
  @spec make_node(ZipperEx.t(), [tree()]) :: ZipperEx.t()
  def make_node(%ZipperEx{loc: loc, module: nil}, children) do
    Zipable.make_node(loc, children)
  end

  def make_node(%ZipperEx{} = zipper, children) do
    Zipable.make_node(zipper, children)
  end

  @doc """
  Appends a `child` to the given `zipper`.

  ## Examples

      iex> alias Support.Zipper
      iex> zipper = Zipper.new(1)
      #ZipperEx<1>
      iex> zipper = Zipper.append_child(zipper, 2)
      #ZipperEx<{1, [2]}>
      iex> Zipper.append_child(zipper, 3)
      #ZipperEx<{1, [2, 3]}>

      iex> alias Support.TreeNode
      iex> zipper = ZipperEx.new(TreeNode.new({1, [2]}))
      iex> ZipperEx.append_child(zipper, TreeNode.new(3))
      #ZipperEx<#TreeNode<1, [#TreeNode<2, []>, #TreeNode<3, []>]>>
  """
  @spec append_child(ZipperEx.t(), tree()) :: ZipperEx.t()
  def append_child(%ZipperEx{loc: loc, module: nil} = zipper, child) do
    case branch?(zipper) do
      true -> do_append_child(zipper, child)
      false -> %ZipperEx{zipper | loc: Zipable.make_node(loc, [child])}
    end
  end

  def append_child(%ZipperEx{} = zipper, child) do
    case branch?(zipper) do
      true -> do_append_child(zipper, child)
      false -> %ZipperEx{zipper | loc: Zipable.make_node(zipper, [child])}
    end
  end

  defp do_append_child(%ZipperEx{} = zipper, child) do
    down = down(zipper)
    up(%ZipperEx{down | right: down.right ++ [child]})
  end

  @doc """
  Returns the leftmost child node of the given `zipper`.

  Returns `nil` if the `zipper` is not a branch.

  ## Examples

      iex> alias Support.TreeNode
      iex> zipper = ZipperEx.new(TreeNode.new({1, [2, 3]}))
      iex> zipper = ZipperEx.down(zipper)
      #ZipperEx<#TreeNode<2, []>>
      iex> ZipperEx.down(zipper)
      nil
  """
  @spec down(ZipperEx.t()) :: ZipperEx.t()
  def down(%ZipperEx{} = zipper) do
    case branch?(zipper) do
      true ->
        [loc | right] = children(zipper)
        %ZipperEx{loc: loc, path: zipper, right: right, module: zipper.module}

      false ->
        nil
    end
  end

  @doc """
  Returns `true` when the zipper has reached the end.

  A zipper reached his end by using `traverse/2`, `traverse/3` or calling
  `next/1` until the end.

  ## Examples

      iex> zipper = Support.Zipper.new({1, [2]})
      iex> ZipperEx.end?(zipper)
      false
      iex> zipper |> ZipperEx.traverse(&Function.identity/1) |> ZipperEx.end?()
      true
      iex> zipper |> ZipperEx.next() |> ZipperEx.next() |> ZipperEx.end?()
      true
  """
  @spec end?(ZipperEx.t()) :: boolean()
  def end?(%ZipperEx{path: path}), do: path == :end

  @doc """
  Returns the first zipper for which `fun` returns a truthy value. If no such
  zipper is found, returns nil.

  Runs through the tree in a depth-first pre-order.

  ## Examples

      iex> alias Support.TreeNode
      iex> zipper = ZipperEx.new(TreeNode.new({1, [2, 3]}))
      iex> ZipperEx.find(zipper, fn node -> ZipperEx.branch?(node) == false end)
      #ZipperEx<#TreeNode<2, []>>
  """
  @spec find(ZipperEx.t() | nil, function()) :: ZipperEx.t() | nil
  def find(nil, _fun), do: nil

  def find(%ZipperEx{} = zipper, fun) when is_function(fun, 1) do
    if fun.(zipper), do: zipper, else: zipper |> next() |> find(fun)
  end

  @doc """
  Inserts a `child` to the given `zipper` at leftmost position.

  ## Examples

      iex> alias Support.TreeNode
      iex> zipper = ZipperEx.new(TreeNode.new({1, [3]}))
      iex> ZipperEx.insert_child(zipper, TreeNode.new(2))
      #ZipperEx<#TreeNode<1, [#TreeNode<2, []>, #TreeNode<3, []>]>>
  """
  @spec insert_child(ZipperEx.t(), tree()) :: ZipperEx.t()
  def insert_child(%ZipperEx{loc: loc, module: nil} = zipper, child) do
    case branch?(zipper) do
      true -> do_insert_child(zipper, child)
      false -> %ZipperEx{zipper | loc: Zipable.make_node(loc, [child])}
    end
  end

  def insert_child(%ZipperEx{} = zipper, child) do
    case branch?(zipper) do
      true -> do_insert_child(zipper, child)
      false -> %ZipperEx{zipper | loc: Zipable.make_node(zipper, [child])}
    end
  end

  defp do_insert_child(%ZipperEx{} = zipper, child) do
    up(%ZipperEx{down(zipper) | left: [child]})
  end

  @doc """
  Inserts a `child` as a left sibling to the given zipper.

  Raises an `ArgumentError` when called with an top level zipper.

  ## Examples

      iex> alias Support.TreeNode
      iex> zipper =
      ...>   TreeNode.new({1, [3]})
      ...>   |> ZipperEx.new()
      ...>   |> ZipperEx.down()
      iex> zipper |> ZipperEx.insert_left(TreeNode.new(2)) |> ZipperEx.top()
      #ZipperEx<#TreeNode<1, [#TreeNode<2, []>, #TreeNode<3, []>]>>
  """
  @spec insert_left(ZipperEx.t(), tree()) :: ZipperEx.t()
  def insert_left(%ZipperEx{path: nil}, _child) do
    raise(ArgumentError, message: "can't insert left sibling at the top level")
  end

  def insert_left(%ZipperEx{left: left} = zipper, child) do
    %ZipperEx{zipper | left: [child | left]}
  end

  @doc """
  Inserts a `child` as a right sibling to the given zipper.

  Raises an `ArgumentError` when called with an top level zipper.

  ## Examples

      iex> alias Support.TreeNode
      iex> zipper =
      ...>   TreeNode.new({1, [2]})
      ...>   |> ZipperEx.new()
      ...>   |> ZipperEx.down()
      iex> zipper |> ZipperEx.insert_right(TreeNode.new(3)) |> ZipperEx.top()
      #ZipperEx<#TreeNode<1, [#TreeNode<2, []>, #TreeNode<3, []>]>>
  """
  @spec insert_right(ZipperEx.t(), tree()) :: ZipperEx.t()
  def insert_right(%ZipperEx{path: nil}, _child) do
    raise(ArgumentError, message: "can't insert right sibling at the top level")
  end

  def insert_right(%ZipperEx{right: right} = zipper, child) do
    %ZipperEx{zipper | right: [child | right]}
  end

  @doc """
  Returns the left sibling of the given `zipper`.

  Returns `nil` if the `zipper` doesn't have a left sibling.

  ## Examples

      iex> alias Support.TreeNode
      iex> zipper =
      ...>   TreeNode.new({1, [10, 11]})
      ...>   |> ZipperEx.new()
      ...>   |> ZipperEx.down()
      iex> zipper = ZipperEx.right(zipper)
      #ZipperEx<#TreeNode<11, []>>
      iex> ZipperEx.right(zipper)
      nil
      iex> zipper = ZipperEx.left(zipper)
      #ZipperEx<#TreeNode<10, []>>
      iex> ZipperEx.left(zipper)
      nil
  """
  @spec left(ZipperEx.t()) :: ZipperEx.t() | nil
  def left(%ZipperEx{left: []}), do: nil

  def left(%ZipperEx{left: [next | left], loc: loc, right: right} = zipper) do
    %ZipperEx{zipper | loc: next, left: left, right: [loc | right]}
  end

  @doc """
  Returns the leftmost sibling of the given `zipper`.

  Returns the `zipper` himself if the given `zipper` is the leftmost sibling.

  ## Examples

      iex> alias Support.TreeNode
      iex> zipper =
      ...>   TreeNode.new({1, [10, 11, 12]})
      ...>   |> ZipperEx.new()
      ...>   |> ZipperEx.down()
      iex> zipper = ZipperEx.rightmost(zipper)
      #ZipperEx<#TreeNode<12, []>>
      iex> ZipperEx.leftmost(zipper)
      #ZipperEx<#TreeNode<10, []>>
  """
  @spec leftmost(ZipperEx.t()) :: ZipperEx.t()
  def leftmost(%ZipperEx{left: []} = zipper), do: zipper

  def leftmost(%ZipperEx{loc: loc, right: right, left: left} = zipper) do
    {left, [leftmost]} = Enum.split(left, -1)
    right = Enum.reverse(left) ++ [loc] ++ right
    %ZipperEx{zipper | loc: leftmost, left: [], right: right}
  end

  @doc """
  Returns a zipper where each node is the result of invoking `fun` on each
  corresponding node of the zipper.

  Runs through the tree in depth-first per-order.

  ## Examples

      iex> alias Support.Zipper
      iex> zipper = Zipper.new({1, [2, {3, [400, 500]}, 6]})
      iex> Zipper.map(zipper, fn
      ...>   {value, children} -> {value * 2, children}
      ...>   value -> value * 2
      ...> end)
      #ZipperEx<{2, [4, {6, [800, 1000]}, 12]}>
  """
  @spec map(ZipperEx.t(), (tree() -> tree())) :: ZipperEx.t()
  def map(%ZipperEx{} = zipper, fun) do
    traverse(zipper, fn node -> update(node, fun) end)
  end

  @doc """
  Returns the next zipper for the given `zipper`.

  The function walks through the tree in a depth-first pre-order. After the last
  Returns to the root after the last zipper and marks the zipper as ended. An
  ended zipper is detectable via `end?/1`. Calling `next/1` for
  an ended `zipper` returns the given `zipper`.

  ## Examples

      iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
      #ZipperEx<{1, [{2, [3, 4]}, 5]}>
      iex> zipper = ZipperEx.next(zipper)
      #ZipperEx<{2, [3, 4]}>
      iex> zipper = ZipperEx.next(zipper)
      #ZipperEx<3>
      iex> zipper = ZipperEx.next(zipper)
      #ZipperEx<4>
      iex> zipper = ZipperEx.next(zipper)
      #ZipperEx<5>
      iex> zipper = ZipperEx.next(zipper)
      #ZipperEx<{1, [{2, [3, 4]}, 5]}>
      iex> ZipperEx.next(zipper)
      #ZipperEx<{1, [{2, [3, 4]}, 5]}>
      iex> ZipperEx.end?(zipper)
      true
  """
  @spec next(ZipperEx.t()) :: ZipperEx.t()
  def next(%ZipperEx{path: :end} = zipper), do: zipper

  def next(%ZipperEx{} = zipper) do
    case branch?(zipper) do
      true -> down(zipper)
      false -> next(zipper, :right)
    end
  end

  defp next(zipper, :right) do
    with nil <- right(zipper) do
      next(zipper, :up)
    end
  end

  defp next(zipper, :up) do
    case up(zipper) do
      nil -> %ZipperEx{zipper | path: :end}
      up -> next(up, :right)
    end
  end

  @doc """
  Returns the node from the given `zipper`.

  ## Examples

      iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
      #ZipperEx<{1, [{2, [3, 4]}, 5]}>
      iex> ZipperEx.node(zipper)
      {1, [{2, [3, 4]}, 5]}
  """
  @spec node(ZipperEx.t()) :: tree()
  def node(%ZipperEx{loc: loc}), do: loc

  @doc """
  Returns the previours zipper for the given `zipper`.

  The function walks through the tree in the opposit direction as `next/1`.
  If no previous zipper is availble, `nil` will be returned.

  ## Examples

      iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
      #ZipperEx<{1, [{2, [3, 4]}, 5]}>
      iex> zipper = ZipperEx.next(zipper)
      #ZipperEx<{2, [3, 4]}>
      iex> zipper = ZipperEx.next(zipper)
      #ZipperEx<3>
      iex> zipper = ZipperEx.prev(zipper)
      #ZipperEx<{2, [3, 4]}>
      iex> zipper = ZipperEx.prev(zipper)
      #ZipperEx<{1, [{2, [3, 4]}, 5]}>
      iex> ZipperEx.prev(zipper)
      nil
  """
  @spec prev(ZipperEx.t()) :: ZipperEx.t()
  def prev(%ZipperEx{path: :end} = zipper) do
    prev(%ZipperEx{zipper | path: nil}, :down)
  end

  def prev(%ZipperEx{} = zipper) do
    case left(zipper) do
      nil -> up(zipper)
      left -> prev(left, :down)
    end
  end

  defp prev(zipper, :down) do
    case branch?(zipper) do
      false -> zipper
      true -> zipper |> down() |> rightmost() |> prev(:down)
    end
  end

  @doc """
  Removes the given `zipper`.

  The function returns the previous `zipper`. If `remove/1` is called with an
  top level zipper an `ArgumentError` will be raised.

  ## Examples

      iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
      #ZipperEx<{1, [{2, [3, 4]}, 5]}>
      iex> zipper = ZipperEx.down(zipper)
      #ZipperEx<{2, [3, 4]}>
      iex> ZipperEx.remove(zipper)
      #ZipperEx<{1, [5]}>
  """
  @spec remove(ZipperEx.t()) :: ZipperEx.t()
  def remove(%ZipperEx{path: nil}) do
    raise(ArgumentError, message: "can't remove the top level node")
  end

  def remove(%ZipperEx{left: [], path: path, right: right}) do
    %ZipperEx{path | loc: make_node(path, right)}
  end

  def remove(%ZipperEx{left: [next | left]} = zipper) do
    prev(%ZipperEx{zipper | left: left, loc: next}, :down)
  end

  @doc """
  Replaces the `zipper` with a `zipper` with the given `node`.

  ## Examples

      iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
      #ZipperEx<{1, [{2, [3, 4]}, 5]}>
      iex> zipper |> ZipperEx.down() |> ZipperEx.replace(7) |> ZipperEx.root()
      {1, [7, 5]}
  """
  @spec replace(ZipperEx.t(), tree()) :: ZipperEx.t()
  def replace(%ZipperEx{} = zipper, node) do
    %ZipperEx{zipper | loc: node}
  end

  @doc """
  Returns the right sibling of the given `zipper`.

  Returns `nil` if the `zipper` doesn't have a right sibling.

  ## Examples

      iex> alias Support.TreeNode
      iex> zipper =
      ...>   TreeNode.new({1, [10, 11]})
      ...>   |> ZipperEx.new()
      ...>   |> ZipperEx.down()
      iex> zipper = ZipperEx.right(zipper)
      #ZipperEx<#TreeNode<11, []>>
      iex> ZipperEx.right(zipper)
      nil
      iex> zipper = ZipperEx.left(zipper)
      #ZipperEx<#TreeNode<10, []>>
      iex> ZipperEx.left(zipper)
      nil
  """
  @spec right(ZipperEx.t()) :: ZipperEx.t() | nil
  def right(%ZipperEx{right: []}), do: nil

  def right(%ZipperEx{right: [next | right]} = zipper) do
    %ZipperEx{zipper | loc: next, right: right, left: [zipper.loc | zipper.left]}
  end

  @doc """
  Returns the rightmost sibling of the given `zipper`.

  Returns the `zipper` himself if the given `zipper` is the rightmost sibling.

  ## Examples

      iex> alias Support.TreeNode
      iex> zipper =
      ...>   TreeNode.new({1, [10, 11, 12]})
      ...>   |> ZipperEx.new()
      ...>   |> ZipperEx.down()
      iex> zipper = ZipperEx.rightmost(zipper)
      #ZipperEx<#TreeNode<12, []>>
      iex> ZipperEx.rightmost(zipper)
      #ZipperEx<#TreeNode<12, []>>
  """
  @spec rightmost(ZipperEx.t()) :: ZipperEx.t()
  def rightmost(%ZipperEx{right: []} = zipper), do: zipper

  def rightmost(%ZipperEx{loc: loc, right: right, left: left} = zipper) do
    {right, [rightmost]} = Enum.split(right, -1)
    left = Enum.reverse(right) ++ [loc] ++ left
    %ZipperEx{zipper | loc: rightmost, right: [], left: left}
  end

  @doc """
  Returns the root node.

  ## Examples

      iex> alias Support.Zipper
      iex> zipper = Zipper.new({1, [{2, [3, 4]}, 5]})
      #ZipperEx<{1, [{2, [3, 4]}, 5]}>
      iex> zipper = zipper |> Zipper.down() |> Zipper.down()
      #ZipperEx<3>
      iex> Zipper.root(zipper)
      {1, [{2, [3, 4]}, 5]}
  """
  @spec root(ZipperEx.t()) :: tree()
  def root(%ZipperEx{} = zipper), do: zipper |> top() |> node()

  @doc """
  Returns the top level zipper.

  ## Examples

      iex> alias Support.Zipper
      iex> zipper = Zipper.new({1, [{2, [3, 4]}, 5]})
      #ZipperEx<{1, [{2, [3, 4]}, 5]}>
      iex> zipper = zipper |> Zipper.down() |> Zipper.down()
      #ZipperEx<3>
      iex> Zipper.top(zipper)
      #ZipperEx<{1, [{2, [3, 4]}, 5]}>
  """
  @spec top(ZipperEx.t()) :: ZipperEx.t()
  def top(%ZipperEx{path: path} = zipper) when path in [nil, :end], do: zipper

  def top(%ZipperEx{} = zipper), do: zipper |> up() |> top()

  @doc """
  Traverses the tree for the given `zipper` in depth-first pre-order and invokes
  `fun` for each zipper along the way.

  If the `zipper` is not at the top, just the subtree will be traversed.

  ## Examples

      iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
      iex> ZipperEx.traverse(zipper, fn z ->
      ...>   ZipperEx.update(z, fn
      ...>     {value, children} -> {value + 100, children}
      ...>     value -> value + 200
      ...>   end)
      ...> end)
      #ZipperEx<{101, [{102, [203, 204]}, 205]}>

      iex> {1, [{2, [3, 4]}, 5]}
      ...> |> Support.Zipper.new()
      ...> |> ZipperEx.down()
      ...> |> ZipperEx.traverse(fn z ->
      ...>   ZipperEx.update(z, fn
      ...>     {value, children} -> {value + 100, children}
      ...>     value -> value + 200
      ...>   end)
      ...> end)
      ...> |> ZipperEx.root()
      {1, [{102, [203, 204]}, 5]}
  """
  @spec traverse(ZipperEx.t(), (ZipperEx.t() -> ZipperEx.t())) :: ZipperEx.t()
  def traverse(%ZipperEx{path: :end} = zipper, fun) when is_function(fun, 1) do
    do_traverse(%ZipperEx{zipper | path: nil}, fun)
  end

  def traverse(%ZipperEx{path: nil} = zipper, fun) when is_function(fun, 1) do
    do_traverse(zipper, fun)
  end

  def traverse(%ZipperEx{} = zipper, fun) when is_function(fun, 1) do
    replace(zipper, zipper |> new() |> do_traverse(fun) |> node())
  end

  defp do_traverse(%ZipperEx{path: :end} = zipper, _fun), do: zipper

  defp do_traverse(zipper, fun) do
    next = next(fun.(zipper))

    do_traverse(next, fun)
  end

  @doc """
  Traverses the tree for the given `zipper` in depth-first pre-order and invokes
  `fun` for each zipper along the way with the accumulator.

  If the `zipper` is not at the top, just the subtree will be traversed.

  ## Examples

      iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
      iex> {zipper, acc} = ZipperEx.traverse(zipper, [], fn z, acc ->
      ...>   updated = ZipperEx.update(z, fn
      ...>     {value, children} -> {value + 100, children}
      ...>     value -> value + 200
      ...>   end)
      ...>   {updated, [ZipperEx.node(z) | acc]}
      ...> end)
      iex> zipper
      #ZipperEx<{101, [{102, [203, 204]}, 205]}>
      iex> acc
      [5, 4, 3, {2, [3, 4]}, {1, [{2, [3, 4]}, 5]}]
  """
  @spec traverse(
          ZipperEx.t(),
          acc,
          (ZipperEx.t(), acc -> {ZipperEx.t(), acc})
        ) :: {ZipperEx.t(), acc}
        when acc: term()
  def traverse(%ZipperEx{path: :end} = zipper, acc, fun) when is_function(fun, 2) do
    do_traverse(%ZipperEx{zipper | path: nil}, acc, fun)
  end

  def traverse(%ZipperEx{path: nil} = zipper, acc, fun) when is_function(fun, 2) do
    do_traverse(zipper, acc, fun)
  end

  def traverse(%ZipperEx{} = zipper, acc, fun) when is_function(fun, 2) do
    sub = new(zipper)
    {replacement, acc} = do_traverse(sub, acc, fun)
    {replace(zipper, node(replacement)), acc}
  end

  defp do_traverse(%ZipperEx{path: :end} = zipper, acc, _fun), do: {zipper, acc}

  defp do_traverse(%ZipperEx{} = zipper, acc, fun) do
    {zipper, acc} = fun.(zipper, acc)
    next = next(zipper)

    do_traverse(next, acc, fun)
  end

  @doc """
  Traverses the tree for the given `zipper` in depth-first pre-order until
  `fun` returns `{:halt, zipper}`. A subtree will be skipped if `fun` returns
  `{:skip, zipper}`.

  If the `zipper` is not at the top, just the subtree will be traversed.

  ## Examples

      iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
      iex> ZipperEx.traverse_while(zipper, fn z ->
      ...>   case ZipperEx.node(z) do
      ...>     {2, _children} ->
      ...>       {:halt, z}
      ...>     _else ->
      ...>       {:cont, ZipperEx.update(z, fn
      ...>         {value, children} -> {value + 100, children}
      ...>         value -> value + 200
      ...>       end)}
      ...>   end
      ...> end)
      #ZipperEx<{101, [{2, [3, 4]}, 5]}>

      iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
      iex> ZipperEx.traverse_while(zipper, fn z ->
      ...>   case ZipperEx.node(z) do
      ...>     {2, _children} ->
      ...>       {:skip, z}
      ...>     _else ->
      ...>       {:cont, ZipperEx.update(z, fn
      ...>         {value, children} -> {value + 100, children}
      ...>         value -> value + 200
      ...>       end)}
      ...>   end
      ...> end)
      #ZipperEx<{101, [{2, [3, 4]}, 205]}>
  """
  @spec traverse_while(
          ZipperEx.t(),
          (ZipperEx.t() ->
             {:cont, ZipperEx.t()}
             | {:halt, ZipperEx.t()}
             | {:skip, ZipperEx.t()})
        ) :: ZipperEx.t()
  def traverse_while(%ZipperEx{path: :end} = zipper, fun) when is_function(fun, 1) do
    do_traverse_while(%ZipperEx{zipper | path: nil}, fun)
  end

  def traverse_while(%ZipperEx{path: nil} = zipper, fun) when is_function(fun, 1) do
    do_traverse_while(zipper, fun)
  end

  def traverse_while(%ZipperEx{} = zipper, fun) when is_function(fun, 1) do
    replace(zipper, zipper |> new() |> do_traverse_while(fun) |> node())
  end

  defp do_traverse_while(%ZipperEx{path: :end} = zipper, _fun), do: zipper

  defp do_traverse_while(zipper, fun) do
    case fun.(zipper) do
      {:cont, cont} -> cont |> next() |> do_traverse_while(fun)
      {:skip, skip} -> skip |> next(:right) |> do_traverse_while(fun)
      {:halt, halt} -> halt |> top() |> Map.put(:path, :end)
    end
  end

  @doc """
  Traverses the tree for the given `zipper` in depth-first pre-order until
  `fun` returns `{:halt, zipper, acc}`. A subtree will be skipped if `fun`
  returns `{:skip, zipper, acc}`.

  If the `zipper` is not at the top, just the subtree will be traversed.

  ## Examples

      iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, {5, [6]}, 7]})
      iex> {zipper, acc} = ZipperEx.traverse_while(zipper, [], fn z, acc ->
      ...>   case ZipperEx.node(z) do
      ...>     {5, _children} ->
      ...>       {:halt, z, acc}
      ...>     _else ->
      ...>       updated = ZipperEx.update(z, fn
      ...>         {value, children} -> {value + 100, children}
      ...>         value -> value + 200
      ...>       end)
      ...>       {:cont, updated, [ZipperEx.node(z) | acc]}
      ...>   end
      ...> end)
      iex> zipper
      #ZipperEx<{101, [{102, [203, 204]}, {5, [6]}, 7]}>
      iex> acc
      [4, 3, {2, [3, 4]}, {1, [{2, [3, 4]}, {5, [6]}, 7]}]

      iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, {5, [6]}, 7]})
      iex> {zipper, acc} = ZipperEx.traverse_while(zipper, [], fn z, acc ->
      ...>   case ZipperEx.node(z) do
      ...>     {5, _children} ->
      ...>       {:skip, z, acc}
      ...>     _else ->
      ...>       updated = ZipperEx.update(z, fn
      ...>         {value, children} -> {value + 100, children}
      ...>         value -> value + 200
      ...>       end)
      ...>       {:cont, updated, [ZipperEx.node(z) | acc]}
      ...>   end
      ...> end)
      iex> zipper
      #ZipperEx<{101, [{102, [203, 204]}, {5, [6]}, 207]}>
      iex> acc
      [7, 4, 3, {2, [3, 4]}, {1, [{2, [3, 4]}, {5, [6]}, 7]}]
  """
  @spec traverse_while(
          ZipperEx.t(),
          acc,
          (ZipperEx.t(), acc ->
             {:cont, ZipperEx.t(), acc}
             | {:halt, ZipperEx.t(), acc}
             | {:skip, ZipperEx.t(), acc})
        ) :: {ZipperEx.t(), acc}
        when acc: term()
  def traverse_while(%ZipperEx{path: :end} = zipper, acc, fun) when is_function(fun, 2) do
    do_traverse_while(%ZipperEx{zipper | path: nil}, acc, fun)
  end

  def traverse_while(%ZipperEx{path: nil} = zipper, acc, fun) when is_function(fun, 2) do
    do_traverse_while(zipper, acc, fun)
  end

  def traverse_while(%ZipperEx{} = zipper, acc, fun) when is_function(fun, 2) do
    sub = new(zipper)
    {replacement, acc} = do_traverse_while(sub, acc, fun)
    {replace(zipper, node(replacement)), acc}
  end

  defp do_traverse_while(%ZipperEx{path: :end} = zipper, acc, _fun), do: {zipper, acc}

  defp do_traverse_while(zipper, acc, fun) do
    case fun.(zipper, acc) do
      {:cont, cont, acc} -> cont |> next() |> do_traverse_while(acc, fun)
      {:skip, skip, acc} -> skip |> next(:right) |> do_traverse_while(acc, fun)
      {:halt, halt, acc} -> {halt |> top() |> Map.put(:path, :end), acc}
    end
  end

  @doc """
  Returns the parent zipper of the given `zipper`.

  Returns `nil` if the `zipper` is the root.

  ## Examples

      iex> alias Support.TreeNode
      iex> zipper = ZipperEx.new(TreeNode.new({1, [2, 3]}))
      iex> zipper = ZipperEx.down(zipper)
      #ZipperEx<#TreeNode<2, []>>
      iex> ZipperEx.up(zipper)
      #ZipperEx<#TreeNode<1, [#TreeNode<2, []>, #TreeNode<3, []>]>>
  """
  @spec up(ZipperEx.t()) :: ZipperEx.t() | nil
  def up(%ZipperEx{path: path}) when path in [nil, :end], do: nil

  def up(%ZipperEx{left: left, loc: loc, path: path, right: right}) do
    children = Enum.reverse(left) ++ [loc] ++ right
    %ZipperEx{path | loc: make_node(path, children)}
  end

  @doc """
  Updates the node of the given `zipper`.

      iex> alias Support.TreeNode
      iex> zipper = ZipperEx.new(TreeNode.new({1, [2, 3]}))
      iex> zipper = ZipperEx.down(zipper)
      iex> zipper = ZipperEx.update(zipper, fn %TreeNode{} = node ->
      ...>   %{node | value: 99}
      ...> end)
      #ZipperEx<#TreeNode<99, []>>
      iex> ZipperEx.root(zipper)
      #TreeNode<1, [#TreeNode<99, []>, #TreeNode<3, []>]>
  """
  @spec update(ZipperEx.t(), (tree() -> tree())) :: ZipperEx.t()
  def update(%ZipperEx{loc: loc} = zipper, fun) when is_function(fun, 1) do
    %ZipperEx{zipper | loc: fun.(loc)}
  end

  defimpl Zipable do
    def branch?(%ZipperEx{module: module, loc: loc}) when not is_nil(module) do
      module.branch?(loc)
    end

    def children(%ZipperEx{module: module, loc: loc}) when not is_nil(module) do
      module.children(loc)
    end

    def make_node(%ZipperEx{module: module, loc: loc}, children)
        when not is_nil(module) do
      module.make_node(loc, children)
    end
  end

  defimpl Inspect do
    def inspect(zipper, _opts), do: "#ZipperEx<#{inspect(zipper.loc)}>"
  end

  defimpl Enumerable do
    def count(_zipper), do: {:error, __MODULE__}

    def member?(_zipper, _value), do: {:error, __MODULE__}

    def slice(_zipper), do: {:error, __MODULE__}

    def reduce(_zipper, {:halt, acc}, _fun), do: {:halted, acc}

    def reduce(%ZipperEx{path: :end}, {:cont, acc}, _fun), do: {:done, acc}

    def reduce(zipper, {:cont, acc}, fun) do
      reduce(ZipperEx.next(zipper), fun.(ZipperEx.node(zipper), acc), fun)
    end
  end
end