#            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
#                    Version 2, December 2004
#
#            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
#   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
#
#  0. You just DO WHAT THE FUCK YOU WANT TO.

defmodule Data.Dictionary.Standard do
  alias Data.Protocol, as: P
  alias __MODULE__, as: T

  defstruct [:dict]

  def new do
    %T{dict: :dict.new}
  end

  def new(enum_or_dict) do
    %T{dict: Data.list(enum_or_dict) |> :dict.from_list}
  end

  def has_key?(%T{dict: self}, key) do
    :dict.is_key(key, self)
  end

  def fetch(%T{dict: self}, key) do
    :dict.find(key, self)
  end

  def put(%T{dict: self}, key, value) do
    %T{dict: :dict.store(key, value, self)}
  end

  def put_new(%T{dict: self}, key, value) do
    if :dict.is_key(key, self) do
      %T{dict: self}
    else
      %T{dict: :dict.store(key, value, self)}
    end
  end

  def delete(%T{dict: self}, key) do
    %T{dict: :dict.erase(key, self)}
  end

  def keys(%T{dict: self}) do
    :dict.fetch_keys(self)
  end

  def values(%T{dict: self}) do
    :dict.fold(fn(_, value, acc) -> [value | acc] end, [], self)
  end

  def reduce(%T{dict: self}, acc, fun) do
    :dict.fold(fn(key, value, acc) -> fun.({ key, value }, acc) end, acc, self)
  end

  def size(%T{dict: self}) do
    :dict.size(self)
  end

  def empty(_) do
    new()
  end

  def to_list(%T{dict: self}) do
    :dict.to_list(self)
  end

  def member?(%T{dict: self}, { key, value }) do
    case :dict.find(key, self) do
      { :ok, ^value } ->
        true

      _ ->
        false
    end
  end

  def member?(%T{dict: self}, key) do
    :dict.is_key(key, self)
  end

  defimpl P.Dictionary do
    defdelegate fetch(self, key), to: T
    defdelegate put(self, key, value), to: T
    defdelegate delete(self, key), to: T
    defdelegate keys(self), to: T
    defdelegate values(self), to: T
  end

  defimpl P.Count do
    defdelegate count(self), to: T, as: :size
  end

  defimpl P.Empty do
    def empty?(self) do
      T.size(self) == 0
    end

    defdelegate clear(self), to: T, as: :empty
  end

  defimpl P.Reduce do
    defdelegate reduce(self, acc, fun), to: T
  end

  defimpl P.ToList do
    defdelegate to_list(self), to: T
  end

  defimpl P.Contains do
    defdelegate contains?(self, key), to: T, as: :member?
  end

  # FIXME: this is crap
  defimpl P.Sequence do
    def first(self) do
      T.reduce(self, nil, fn(x, _) -> throw { :first, x } end)

      nil
    catch
      { :first, x } ->
        x
    end

    def next(self) do
      case T.to_list(self) do
        [] ->
          nil

        [_] ->
          nil

        [_ | tail] ->
          tail
      end
    end
  end

  defimpl P.Into do
    def into(self, { key, value }) do
      self |> T.put(key, value)
    end
  end

  defimpl Enumerable do
    use Data.Enumerable
  end

  defimpl Collectable do
    use Data.Collectable
  end

  defimpl Inspect do
    import Inspect.Algebra

    def inspect(self, opts) do
      concat ["#Dictionary<", to_doc(T.to_list(self), opts), ">"]
    end
  end
end
