defmodule Deque do
  @moduledoc """
  A fast deque implementation using 2 rotating lists.
  """

  @opaque t :: %__MODULE__{
    size: integer,
    max_size: integer,
    list1: list,
    list2: list,
  }
  @type value :: term

  defstruct size: 0, max_size: nil, list1: [], list2: []

  @spec new(integer) :: t
  def new(max_size \\ 100) do
    %Deque{max_size: max_size}
  end

  @spec append(t, value) :: t
  def append(%Deque{size: size, max_size: max_size, list1: [], list2: list2}=deque, value) when size < max_size do
    %{deque | size: size + 1, list2: [value|list2]}
  end
  def append(%Deque{size: size, max_size: max_size, list2: list2}=deque, value) when size < max_size do
    %{deque | size: size + 1, list2: [value|list2]}
  end
  def append(%Deque{list1: [], list2: list2}=deque, value) do
    %{deque | list1: Enum.reverse(list2), list2: []} |> append(value)
  end
  def append(%Deque{list1: [_|list1], list2: list2}=deque, value) do
    %{deque | list1: list1, list2: [value|list2]}
  end

  @spec appendleft(t, value) :: t
  def appendleft(%Deque{size: size, max_size: max_size, list1: list1, list2: []}=deque, value) when size < max_size do
    %{deque | size: size + 1, list1: [value|list1]}
  end
  def appendleft(%Deque{size: size, max_size: max_size, list1: list1}=deque, value) when size < max_size do
    %{deque | size: size + 1, list1: [value|list1]}
  end
  def appendleft(%Deque{list1: list1, list2: []}=deque, value) do
    %{deque | list1: [], list2: Enum.reverse(list1)} |> appendleft(value)
  end
  def appendleft(%Deque{list1: list1, list2: [_|list2]}=deque, value) do
    %{deque | list1: [value|list1], list2: list2}
  end

  @spec pop(t) :: {value | nil, t}
  def pop(%Deque{list1: [], list2: []}=deque) do
    {nil, deque}
  end
  def pop(%Deque{size: size, list2: [value|list2]}=deque) do
    {value, %{deque | size: size - 1, list2: list2}}
  end
  def pop(%Deque{list1: list1}=deque) do
    %{deque | list1: [], list2: Enum.reverse(list1)} |> pop
  end

  @spec popleft(t) :: {value | nil, t}
  def popleft(%Deque{list1: [], list2: []}=deque) do
    {nil, deque}
  end
  def popleft(%Deque{size: size, list1: [value|list1]}=deque) do
    {value, %{deque | size: size - 1, list1: list1}}
  end
  def popleft(%Deque{list2: list2}=deque) do
    %{deque | list1: Enum.reverse(list2), list2: []} |> popleft
  end

  @spec last(t) :: value | nil
  def last(%Deque{list1: [], list2: []}), do: nil
  def last(%Deque{list2: [value|_]}), do: value
  def last(%Deque{list1: list1}=deque) do
    %{deque | list1: [], list2: Enum.reverse(list1)} |> last
  end

  @spec first(t) :: value | nil
  def first(%Deque{list1: [], list2: []}), do: nil
  def first(%Deque{list1: [value|_]}), do: value
  def first(%Deque{list2: list2}=deque) do
    %{deque | list1: Enum.reverse(list2), list2: []} |> first
  end

  @spec clear(t) :: t
  def clear(%Deque{max_size: max_size}), do: new(max_size)

  @spec take_while(t, (term -> boolean)) :: t
  def take_while(%Deque{list1: [], list2: []}=deque, _func), do: deque
  def take_while(%Deque{list1: list1, list2: list2}=deque, func) do
    case lazy_take_while(list2, func) do
      # If the tail list halts, then everything in head list is invalid.
      {:halt, list2_n, list2} ->
        %{deque | size: list2_n, list1: [], list2: Enum.reverse(list2)}
      {list2_n, list2} ->
        # Halting does not matter when filtering the head list. Reverse the list
        # before attempting to filter it, it will automatically be reversed again.
        {list1_n, list1} =
          with {:halt, list1_n, list1} <- lazy_take_while(Enum.reverse(list1), func) do
            {list1_n, list1}
          end
        %{deque | size: list1_n + list2_n, list1: list1, list2: Enum.reverse(list2)}
    end
  end

  ## Private

  defp lazy_take_while(list, func), do: lazy_take_while(list, [], 0, func)

  defp lazy_take_while([], acc, n, _func), do: {n, acc}
  defp lazy_take_while([h | t], acc, n, func) do
    if func.(h) do
      lazy_take_while(t, [h | acc], n + 1, func)
    else
      {:halt, n, acc}
    end
  end

  ## Protocols

  defimpl Enumerable do
    def reduce(_, {:halt, acc}, _fun) do
      {:halted, acc}
    end
    def reduce(deque, {:suspend, acc}, fun) do
      {:suspended, acc, &reduce(deque, &1, fun)}
    end
    def reduce(%Deque{list1: list1, list2: list2}, {:cont, acc}, fun) do
      reduce({list1, list2}, {:cont, acc}, fun)
    end
    def reduce({[], []}, {:cont, acc}, _fun) do
      {:done, acc}
    end
    def reduce({[h|list1], list2}, {:cont, acc}, fun) do
      reduce({list1, list2}, fun.(h, acc), fun)
    end
    def reduce({[], list2}, {:cont, acc}, fun) do
      reduce({Enum.reverse(list2), []}, {:cont, acc}, fun)
    end

    def member?(%Deque{list1: list1, list2: list2}, element) do
      {:ok, element in list1 or element in list2}
    end

    def count(%Deque{size: size}) do
      {:ok, size}
    end

    def slice(%Deque{}) do
      {:error, __MODULE__}
    end
  end

  defimpl Collectable do
    def into(original) do
      {original, fn
        deque, {:cont, value} -> Deque.append(deque, value)
        deque, :done -> deque
        _, :halt -> :ok
      end}
    end
  end

  defimpl Inspect do
    import Inspect.Algebra

    def inspect(deque, opts) do
      concat ["#Deque<", Inspect.List.inspect(Enum.to_list(deque), opts), ">"]
    end
  end
end
