defmodule CubDB.Btree.KeyRange do
  @moduledoc false

  # `CubDB.Btree.KeyRange` is a module implementing the `Enumerable` protocol to
  # iterate through a range of entries on a Btree bounded by a minimum and
  # maximum key. The bounds can be exclusive or inclusive: bounds are either
  # `nil` or tuples of `{key, boolean}`, where the boolean indicates whether the
  # bound is inclusive or not. This is primarily used for selection operations.

  alias CubDB.Btree
  alias CubDB.Btree.KeyRange

  @type bound :: {Btree.key(), boolean} | nil
  @type t :: %KeyRange{btree: Btree.t(), min_key: bound, max_key: bound, reverse: boolean}

  @enforce_keys [:btree]
  defstruct btree: nil, min_key: nil, max_key: nil, reverse: false

  @spec new(Btree.t(), bound, bound, boolean) :: KeyRange.t()
  def new(btree, min_key \\ nil, max_key \\ nil, reverse \\ false) do
    %KeyRange{btree: btree, min_key: min_key, max_key: max_key, reverse: reverse}
  end
end

defimpl Enumerable, for: CubDB.Btree.KeyRange do
  alias CubDB.Btree
  alias CubDB.Store
  alias CubDB.Btree.KeyRange

  import Btree, only: [leaf: 1, branch: 1, value: 1, deleted: 0]

  def reduce(key_range, cmd_acc, fun) do
    %KeyRange{btree: btree, min_key: min_key, max_key: max_key, reverse: reverse} = key_range
    Btree.Enumerable.reduce(btree, cmd_acc, fun, &get_children(min_key, max_key, reverse, &1, &2))
  end

  def count(_), do: {:error, __MODULE__}

  def member?(%KeyRange{min_key: {min, true}, max_key: _}, {key, _}) when key < min do
    {:ok, false}
  end

  def member?(%KeyRange{min_key: _, max_key: {max, true}}, {key, _}) when key > max do
    {:ok, false}
  end

  def member?(%KeyRange{min_key: {min, false}, max_key: _}, {key, _}) when key <= min do
    {:ok, false}
  end

  def member?(%KeyRange{min_key: _, max_key: {max, false}}, {key, _}) when key >= max do
    {:ok, false}
  end

  def member?(%KeyRange{btree: btree}, {key, value}) do
    case Btree.fetch(btree, key) do
      {:ok, ^value} -> {:ok, true}
      _ -> {:ok, false}
    end
  end

  def member?(_, _), do: {:ok, false}

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

  defp get_children(min_key, max_key, reverse, branch(children: locs), store) do
    children =
      locs
      |> Enum.chunk_every(2, 1)
      |> Enum.filter(fn
        [{key, _}, {next_key, _}] -> filter_branch(min_key, max_key, key, next_key)
        [{key, _}] -> filter_branch(nil, max_key, key, nil)
      end)
      |> Enum.map(fn [{k, loc} | _] ->
        {k, Store.get_node(store, loc)}
      end)

    if reverse, do: Enum.reverse(children), else: children
  end

  defp get_children(min_key, max_key, reverse, leaf(children: locs), store) do
    children =
      locs
      |> Enum.filter(fn {key, _} ->
        filter_leave(min_key, max_key, key)
      end)
      |> Enum.map(fn {k, loc} ->
        {k, Store.get_node(store, loc)}
      end)
      |> Enum.filter(fn {_, node} ->
        node != deleted()
      end)

    if reverse, do: Enum.reverse(children), else: children
  end

  defp get_children(_, _, _, value(val: v), _), do: v

  defp filter_branch(nil, nil, _, _), do: true
  defp filter_branch(nil, {max, true}, key, _), do: key <= max
  defp filter_branch(nil, {max, false}, key, _), do: key < max
  defp filter_branch({min, _}, nil, _, next_key), do: next_key > min
  defp filter_branch({min, _}, {max, true}, key, next_key), do: key <= max && next_key > min
  defp filter_branch({min, _}, {max, false}, key, next_key), do: key < max && next_key > min

  defp filter_leave(nil, nil, _), do: true
  defp filter_leave({min, true}, nil, key), do: key >= min
  defp filter_leave({min, false}, nil, key), do: key > min
  defp filter_leave(nil, {max, true}, key), do: key <= max
  defp filter_leave(nil, {max, false}, key), do: key < max
  defp filter_leave({min, true}, {max, true}, key), do: key >= min && key <= max
  defp filter_leave({min, false}, {max, true}, key), do: key > min && key <= max
  defp filter_leave({min, true}, {max, false}, key), do: key >= min && key < max
  defp filter_leave({min, false}, {max, false}, key), do: key > min && key < max
end
