Public API WIP

Public API WIP

defmodule FunWithFlags do
  @moduledoc """
  FunWithFlags, the Elixir feature flag library.

  This module provides the public interface to the library and its API is
  made of three simple methods to enable, disable and query feature flags.

  In their simplest form, flags can be toggled on and off globally.

  More advanced rules or "gates" are available, and they can be set and queried
  for any term that implements these protocols:

  * The `FunWithFlags.Actor` protocol can be
  implemented for types and structs that should have specific rules. For
  example, in web applications it's common to use a `%User{}` struct or
  equivalent as an actor, or perhaps the current country of the request.

  * The `FunWithFlags.Group` protocol can be
  implemented for types and structs that should belong to groups for which
  one wants to enable and disable some flags. For example, one could implement
  the protocol for a `%User{}` struct to identify administrators.

  See the [Usage](/fun_with_flags/readme.html#usage) notes for a more detailed
  explanation.
  """

  alias FunWithFlags.{Flag, Gate}

  @store FunWithFlags.Config.store_module

  @type options :: Keyword.t

  @doc """
  Checks if a flag is enabled.

  It can be invoked with just the flag name, as an atom,
  to check the general staus of a flag (i.e. the boolean gate).

  ## Options

  * `:for` - used to provide a term for which the flag could
  have a specific value. The passed term should implement the
  `Actor` or `Group` protocol, or both.

  ## Examples

  This example relies on the [reference implementation](https://github.com/tompave/fun_with_flags/blob/master/test/support/test_user.ex)
  used in the tests.

      iex> alias FunWithFlags.TestUser, as: User
      iex> harry = %User{id: 1, name: "Harry Potter", groups: ["wizards", "gryffindor"]}
      iex> FunWithFlags.disable(:elder_wand)
      iex> FunWithFlags.enable(:elder_wand, for_actor: harry)
      iex> FunWithFlags.enabled?(:elder_wand)
      false
      iex> FunWithFlags.enabled?(:elder_wand, for: harry)
      true
      iex> voldemort = %User{id: 7, name: "Tom Riddle", groups: ["wizards", "slytherin"]}
      iex> FunWithFlags.enabled?(:elder_wand, for: voldemort)
      false
      iex> filch = %User{id: 88, name: "Argus Filch", groups: ["staff"]}
      iex> FunWithFlags.enable(:magic_wands, for_group: "wizards")
      iex> FunWithFlags.enabled?(:magic_wands, for: harry)
      true
      iex> FunWithFlags.enabled?(:magic_wands, for: voldemort)
      true
      iex> FunWithFlags.enabled?(:magic_wands, for: filch)
      false

  """
  @spec enabled?(atom, options) :: boolean
  def enabled?(flag_name, options \\ [])

  def enabled?(flag_name, []) when is_atom(flag_name) do
    case @store.lookup(flag_name) do
      {:ok, flag} -> Flag.enabled?(flag)
      _           -> false
    end
  end

  def enabled?(flag_name, [for: nil]) do
    enabled?(flag_name)
  end

  def enabled?(flag_name, [for: item]) when is_atom(flag_name) do
    case @store.lookup(flag_name) do
      {:ok, flag} -> Flag.enabled?(flag, for: item)
      _           -> false
    end
  end

  @doc """
  Enables a feature flag.

  ## Options

  * `:for_actor` - used to enable the flag for a specific term only.
  The value can be any term that implements the `Actor` protocol.
  * `:for_group` - used to enable the flag for a specific group only.
  The value should be a binary or an atom (It's internally converted
  to a binary and it's stored and retrieved as a binary. Atoms are
  supported for retro-compatibility with versions <= 0.9)
  * `:for_percentage_of` - used to enable the flag for a percentage
  of time or actors, expressed as `{:time, float}` or `{:actors, float}`,
  where float is in the range `0.0 < x < 1.0`.

  ## Examples

  ### Enable globally

      iex> FunWithFlags.enabled?(:super_shrink_ray)
      false
      iex> FunWithFlags.enable(:super_shrink_ray)
      {:ok, true}
      iex> FunWithFlags.enabled?(:super_shrink_ray)
      true

  ### Enable for an actor

      iex> FunWithFlags.disable(:warp_drive)
      {:ok, false}
      iex> FunWithFlags.enable(:warp_drive, for_actor: "Scotty")
      {:ok, true}
      iex> FunWithFlags.enabled?(:warp_drive)
      false
      iex> FunWithFlags.enabled?(:warp_drive, for: "Scotty")
      true

  ### Enable for a group

  This example relies on the [reference implementation](https://github.com/tompave/fun_with_flags/blob/master/test/support/test_user.ex)
  used in the tests.

      iex> alias FunWithFlags.TestUser, as: User
      iex> marty = %User{name: "Marty McFly", groups: ["students", "time_travelers"]}
      iex> doc = %User{name: "Emmet Brown", groups: ["scientists", "time_travelers"]}
      iex> buford = %User{name: "Buford Tannen", groups: ["gunmen", "bandits"]}
      iex> FunWithFlags.enable(:delorean, for_group: "time_travelers")
      {:ok, true}
      iex> FunWithFlags.enabled?(:delorean)
      false
      iex> FunWithFlags.enabled?(:delorean, for: buford)
      false
      iex> FunWithFlags.enabled?(:delorean, for: marty)
      true
      iex> FunWithFlags.enabled?(:delorean, for: doc)
      true

  ### Enable for a percentage of the time

      iex> FunWithFlags.disable(:random_glitch)
      iex> FunWithFlags.enable(:random_glitch, for_percentage_of: {:time, 0.999999999})
      iex> FunWithFlags.enabled?(:random_glitch)
      true
      iex> FunWithFlags.enable(:random_glitch, for_percentage_of: {:time, 0.000000001})
      iex> FunWithFlags.enabled?(:random_glitch)
      false

  ### Enable for a percentage of the actors

  This example is based on the fact that the actor score for the actor-flag pair
  `marty + :new_ui` is lower than 50%, and for the `buford + :new_ui` is higher.

      iex> FunWithFlags.disable(:new_ui)
      iex> FunWithFlags.enable(:new_ui, for_percentage_of: {:actors, 0.5})
      iex> FunWithFlags.enabled?(:new_ui)
      false
      iex> alias FunWithFlags.TestUser, as: User
      iex> marty = %User{id: 42, name: "Marty McFly"}
      iex> buford = %User{id: 2, name: "Buford Tannen"}
      iex> FunWithFlags.enabled?(:new_ui, for: marty)
      true
      iex> FunWithFlags.enabled?(:new_ui, for: buford)
      false

  """
  @spec enable(atom, options) :: {:ok, true}
  def enable(flag_name, options \\ [])

  def enable(flag_name, []) when is_atom(flag_name) do
    {:ok, flag} = @store.put(flag_name, Gate.new(:boolean, true))
    verify(flag)
  end

  def enable(flag_name, [for_actor: nil]) do
    enable(flag_name)
  end

  def enable(flag_name, [for_actor: actor]) when is_atom(flag_name) do
    gate = Gate.new(:actor, actor, true)
    {:ok, flag} = @store.put(flag_name, gate)
    verify(flag, for: actor)
  end

  def enable(flag_name, [for_group: nil]) do
    enable(flag_name)
  end

  def enable(flag_name, [for_group: group_name]) when is_atom(flag_name) do
    gate = Gate.new(:group, group_name, true)
    {:ok, _flag} = @store.put(flag_name, gate)
    {:ok, true}
  end

  def enable(flag_name, [for_percentage_of: {:time, ratio}]) when is_atom(flag_name) do
    gate = Gate.new(:percentage_of_time, ratio)
    {:ok, _flag} = @store.put(flag_name, gate)
    {:ok, true}
  end

  def enable(flag_name, [for_percentage_of: {:actors, ratio}]) when is_atom(flag_name) do
    gate = Gate.new(:percentage_of_actors, ratio)
    {:ok, _flag} = @store.put(flag_name, gate)
    {:ok, true}
  end

  @doc """
  Disables a feature flag.

  ## Options

  * `:for_actor` - used to disable the flag for a specific term only.
  The value can be any term that implements the `Actor` protocol.
  * `:for_group` - used to disable the flag for a specific group only.
   The value should be a binary or an atom (It's internally converted
  to a binary and it's stored and retrieved as a binary. Atoms are
  supported for retro-compatibility with versions <= 0.9)
  * `:for_percentage_of` - used to disable the flag for a percentage
  of time or actors, expressed as `{:time, float}` or `{:actors, float}`,
  where float is in the range `0.0 < x < 1.0`.

  ## Examples

  ### Disable globally

      iex> FunWithFlags.enable(:random_koala_gifs)
      iex> FunWithFlags.enabled?(:random_koala_gifs)
      true
      iex> FunWithFlags.disable(:random_koala_gifs)
      {:ok, false}
      iex> FunWithFlags.enabled?(:random_koala_gifs)
      false

  ## Disable for an actor

      iex> FunWithFlags.enable(:spider_sense)
      {:ok, true}
      iex> villain = %{name: "Venom"}
      iex> FunWithFlags.disable(:spider_sense, for_actor: villain)
      {:ok, false}
      iex> FunWithFlags.enabled?(:spider_sense)
      true
      iex> FunWithFlags.enabled?(:spider_sense, for: villain)
      false

  ### Disable for a group

  This example relies on the [reference implementation](https://github.com/tompave/fun_with_flags/blob/master/test/support/test_user.ex)
  used in the tests.

      iex> alias FunWithFlags.TestUser, as: User
      iex> harry = %User{name: "Harry Potter", groups: ["wizards", "gryffindor"]}
      iex> dudley = %User{name: "Dudley Dursley", groups: ["muggles"]}
      iex> FunWithFlags.enable(:hogwarts)
      {:ok, true}
      iex> FunWithFlags.disable(:hogwarts, for_group: "muggles")
      {:ok, false}
      iex> FunWithFlags.enabled?(:hogwarts)
      true
      iex> FunWithFlags.enabled?(:hogwarts, for: harry)
      true
      iex> FunWithFlags.enabled?(:hogwarts, for: dudley)
      false

  ### Disable for a percentage of the time

      iex> FunWithFlags.clear(:random_glitch)
      :ok
      iex> FunWithFlags.disable(:random_glitch, for_percentage_of: {:time, 0.999999999})
      {:ok, false}
      iex> FunWithFlags.enabled?(:random_glitch)
      false
      iex> FunWithFlags.disable(:random_glitch, for_percentage_of: {:time, 0.000000001})
      {:ok, false}
      iex> FunWithFlags.enabled?(:random_glitch)
      true

  ### Disable for a percentage of the actors

      iex> FunWithFlags.disable(:new_ui, for_percentage_of: {:actors, 0.3})
      {:ok, false}

  """
  @spec disable(atom, options) :: {:ok, false}
  def disable(flag_name, options \\ [])

  def disable(flag_name, []) when is_atom(flag_name) do
    {:ok, flag} = @store.put(flag_name, Gate.new(:boolean, false))
    verify(flag)
  end

  def disable(flag_name, [for_actor: nil]) do
    disable(flag_name)
  end

  def disable(flag_name, [for_actor: actor]) when is_atom(flag_name) do
    gate = Gate.new(:actor, actor, false)
    {:ok, flag} = @store.put(flag_name, gate)
    verify(flag, for: actor)
  end

  def disable(flag_name, [for_group: nil]) do
    disable(flag_name)
  end

  def disable(flag_name, [for_group: group_name]) when is_atom(flag_name) do
    gate = Gate.new(:group, group_name, false)
    {:ok, _flag} = @store.put(flag_name, gate)
    {:ok, false}
  end

  def disable(flag_name, [for_percentage_of: {type, ratio}])
  when is_atom(flag_name) and is_float(ratio) do
    inverted_ratio = 1.0 - ratio
    {:ok, true} = enable(flag_name, [for_percentage_of: {type, inverted_ratio}])
    {:ok, false}
  end

  @doc """
  Clears the data of a feature flag.

  Clears the data for an entire feature flag or for a specific
  Actor or Group gate. Clearing a boolean gate is not supported
  because a missing boolean gate is equivalent to a disabled boolean
  gate.

  Sometimes enabling or disabling a gate is not what you want, and you
  need to remove that gate's rules instead. For example, if you don't need
  anymore to explicitly enable or disable a flag for an actor, and the
  default state should be used instead, you'll want to cleare the gate.

  It's also possible to clear the entire flag, by not passing any option.

  ## Options

  * `for_actor: an_actor` - used to clear the flag for a specific term only.
  The value can be any term that implements the `Actor` protocol.
  * `for_group: a_group_name` - used to clear the flag for a specific group only.
   The value should be a binary or an atom (It's internally converted
  to a binary and it's stored and retrieved as a binary. Atoms are
  supported for retro-compatibility with versions <= 0.9)
  * `boolean: true` - used to clear the boolean gate.
  * `for_percentage: true` - used to clear any percentage gate.

  ## Examples

      iex> alias FunWithFlags.TestUser, as: User
      iex> harry = %User{id: 1, name: "Harry Potter", groups: ["wizards", "gryffindor"]}
      iex> hagrid = %User{id: 2, name: "Rubeus Hagrid", groups: ["wizards", "gamekeeper"]}
      iex> dudley = %User{id: 3, name: "Dudley Dursley", groups: ["muggles"]}
      iex> FunWithFlags.disable(:wands)
      iex> FunWithFlags.enable(:wands, for_group: "wizards")
      iex> FunWithFlags.disable(:wands, for_actor: hagrid)
      iex>
      iex> FunWithFlags.enabled?(:wands)
      false
      iex> FunWithFlags.enabled?(:wands, for: harry)
      true
      iex> FunWithFlags.enabled?(:wands, for: hagrid)
      false
      iex> FunWithFlags.enabled?(:wands, for: dudley)
      false
      iex>
      iex> FunWithFlags.clear(:wands, for_actor: hagrid)
      :ok
      iex> FunWithFlags.enabled?(:wands, for: hagrid)
      true
      iex>
      iex> FunWithFlags.clear(:wands)
      :ok
      iex> FunWithFlags.enabled?(:wands)
      false
      iex> FunWithFlags.enabled?(:wands, for: harry)
      false
      iex> FunWithFlags.enabled?(:wands, for: hagrid)
      false
      iex> FunWithFlags.enabled?(:wands, for: dudley)
      false

  """
  @spec clear(atom, options) :: :ok
  def clear(flag_name, options \\ [])

  def clear(flag_name, []) when is_atom(flag_name) do
    {:ok, _flag} = @store.delete(flag_name)
    :ok
  end

  def clear(flag_name, [boolean: true]) do
    gate = Gate.new(:boolean, false) # we only care about the gate id
    _clear_gate(flag_name, gate)
  end

  def clear(flag_name, [for_actor: nil]) do
    clear(flag_name)
  end

  def clear(flag_name, [for_actor: actor]) when is_atom(flag_name) do
    gate = Gate.new(:actor, actor, false) # we only care about the gate id
    _clear_gate(flag_name, gate)
  end

  def clear(flag_name, [for_group: nil]) do
    clear(flag_name)
  end

  def clear(flag_name, [for_group: group_name]) when is_atom(flag_name) do
    gate = Gate.new(:group, group_name, false) # we only care about the gate id
    _clear_gate(flag_name, gate)
  end

  def clear(flag_name, [for_percentage: true]) do
    gate = Gate.new(:percentage_of_time, 0.5) # we only care about the gate id
    _clear_gate(flag_name, gate)
  end

  defp _clear_gate(flag_name, gate) do
    {:ok, _flag} = @store.delete(flag_name, gate)
    :ok
  end

  @doc """
  Returns a list of all flag names currently configured, as atoms.

  This can be useful for debugging or for display purposes,
  but it's not meant to be used at runtime. Undefined flags,
  for example, will be considered disabled.
  """
  @spec all_flag_names() :: {:ok, [atom]} | {:ok, []}
  defdelegate all_flag_names(), to: @store

  @doc """
  Returns a list of all the flags currently configured, as data structures.

  This function is provided for debugging and to build more complex
  functionality (e.g. it's used in the web GUI), but it is not meant to be
  used at runtime to check if a flag is enabled.

  To query the value of a flag, please use the `enabled?2` function instead.
  """
  @spec all_flags() :: {:ok, [FunWithFlags.Flag.t]} | {:ok, []}
  defdelegate all_flags(), to: @store

  @doc """
  Returns a `FunWithFlags.Flag` struct for the given name, or `nil` if
  no flag is found.

  Useful for debugging.
  """
  @spec get_flag(atom) :: FunWithFlags.Flag.t | nil
  def get_flag(name) do
    {:ok, names} = all_flag_names()
    if name in names do
      {:ok, flag} = FunWithFlags.Store.Persistent.adapter.get(name)
      flag
    else
      nil
    end
  end

  defp verify(flag) do
    {:ok, Flag.enabled?(flag)}
  end
  defp verify(flag, [for: data]) do
    {:ok, Flag.enabled?(flag, for: data)}
  end
end

defmodule FunWithFlags do
  @moduledoc """
  FunWithFlags, the Elixir feature flag library.

  This module provides the public interface to the library and its API is
  made of three simple methods to enable, disable and query feature flags.

  In their simplest form, flags can be toggled on and off globally.

  More advanced rules or "gates" are available, and they can be set and queried
  for any term that implements these protocols:

  * The `FunWithFlags.Actor` protocol can be
  implemented for types and structs that should have specific rules. For
  example, in web applications it's common to use a `%User{}` struct or
  equivalent as an actor, or perhaps the current country of the request.

  * The `FunWithFlags.Group` protocol can be
  implemented for types and structs that should belong to groups for which
  one wants to enable and disable some flags. For example, one could implement
  the protocol for a `%User{}` struct to identify administrators.

  See the [Usage](/fun_with_flags/readme.html#usage) notes for a more detailed
  explanation.
  """

  alias FunWithFlags.{Flag, Gate}

  @store FunWithFlags.Config.store_module

  @type options :: Keyword.t

  @doc """
  Checks if a flag is enabled.

  It can be invoked with just the flag name, as an atom,
  to check the general staus of a flag (i.e. the boolean gate).

  ## Options

  * `:for` - used to provide a term for which the flag could
  have a specific value. The passed term should implement the
  `Actor` or `Group` protocol, or both.

  ## Examples

  This example relies on the [reference implementation](https://github.com/tompave/fun_with_flags/blob/master/test/support/test_user.ex)
  used in the tests.

      iex> alias FunWithFlags.TestUser, as: User
      iex> harry = %User{id: 1, name: "Harry Potter", groups: ["wizards", "gryffindor"]}
      iex> FunWithFlags.disable(:elder_wand)
      iex> FunWithFlags.enable(:elder_wand, for_actor: harry)
      iex> FunWithFlags.enabled?(:elder_wand)
      false
      iex> FunWithFlags.enabled?(:elder_wand, for: harry)
      true
      iex> voldemort = %User{id: 7, name: "Tom Riddle", groups: ["wizards", "slytherin"]}
      iex> FunWithFlags.enabled?(:elder_wand, for: voldemort)
      false
      iex> filch = %User{id: 88, name: "Argus Filch", groups: ["staff"]}
      iex> FunWithFlags.enable(:magic_wands, for_group: "wizards")
      iex> FunWithFlags.enabled?(:magic_wands, for: harry)
      true
      iex> FunWithFlags.enabled?(:magic_wands, for: voldemort)
      true
      iex> FunWithFlags.enabled?(:magic_wands, for: filch)
      false

  """
  @spec enabled?(atom, options) :: boolean
  def enabled?(flag_name, options \\ [])

  def enabled?(flag_name, []) when is_atom(flag_name) do
    case @store.lookup(flag_name) do
      {:ok, flag} -> Flag.enabled?(flag)
      _           -> false
    end
  end

  def enabled?(flag_name, [for: nil]) do
    enabled?(flag_name)
  end

  def enabled?(flag_name, [for: item]) when is_atom(flag_name) do
    case @store.lookup(flag_name) do
      {:ok, flag} -> Flag.enabled?(flag, for: item)
      _           -> false
    end
  end

  @doc """
  Enables a feature flag.

  ## Options

  * `:for_actor` - used to enable the flag for a specific term only.
  The value can be any term that implements the `Actor` protocol.
  * `:for_group` - used to enable the flag for a specific group only.
  The value should be a binary or an atom (It's internally converted
  to a binary and it's stored and retrieved as a binary. Atoms are
  supported for retro-compatibility with versions <= 0.9)
  * `:for_percentage_of` - used to enable the flag for a percentage
  of time expressed as `{:time, float}`, where float is in the range
  `0.0 < x < 1.0`.

  ## Examples

  ### Enable globally

      iex> FunWithFlags.enabled?(:super_shrink_ray)
      false
      iex> FunWithFlags.enable(:super_shrink_ray)
      {:ok, true}
      iex> FunWithFlags.enabled?(:super_shrink_ray)
      true

  ### Enable for an actor

      iex> FunWithFlags.disable(:warp_drive)
      {:ok, false}
      iex> FunWithFlags.enable(:warp_drive, for_actor: "Scotty")
      {:ok, true}
      iex> FunWithFlags.enabled?(:warp_drive)
      false
      iex> FunWithFlags.enabled?(:warp_drive, for: "Scotty")
      true

  ### Enable for a group

  This example relies on the [reference implementation](https://github.com/tompave/fun_with_flags/blob/master/test/support/test_user.ex)
  used in the tests.

      iex> alias FunWithFlags.TestUser, as: User
      iex> marty = %User{name: "Marty McFly", groups: ["students", "time_travelers"]}
      iex> doc = %User{name: "Emmet Brown", groups: ["scientists", "time_travelers"]}
      iex> buford = %User{name: "Buford Tannen", groups: ["gunmen", "bandits"]}
      iex> FunWithFlags.enable(:delorean, for_group: "time_travelers")
      {:ok, true}
      iex> FunWithFlags.enabled?(:delorean)
      false
      iex> FunWithFlags.enabled?(:delorean, for: buford)
      false
      iex> FunWithFlags.enabled?(:delorean, for: marty)
      true
      iex> FunWithFlags.enabled?(:delorean, for: doc)
      true

  ### Enable for a percentage of the time

      iex> FunWithFlags.disable(:random_glitch)
      iex> FunWithFlags.enable(:random_glitch, for_percentage_of: {:time, 0.999999999})
      iex> FunWithFlags.enabled?(:random_glitch)
      true
      iex> FunWithFlags.enable(:random_glitch, for_percentage_of: {:time, 0.000000001})
      iex> FunWithFlags.enabled?(:random_glitch)
      false

  """
  @spec enable(atom, options) :: {:ok, true}
  def enable(flag_name, options \\ [])

  def enable(flag_name, []) when is_atom(flag_name) do
    {:ok, flag} = @store.put(flag_name, Gate.new(:boolean, true))
    verify(flag)
  end

  def enable(flag_name, [for_actor: nil]) do
    enable(flag_name)
  end

  def enable(flag_name, [for_actor: actor]) when is_atom(flag_name) do
    gate = Gate.new(:actor, actor, true)
    {:ok, flag} = @store.put(flag_name, gate)
    verify(flag, for: actor)
  end

  def enable(flag_name, [for_group: nil]) do
    enable(flag_name)
  end

  def enable(flag_name, [for_group: group_name]) when is_atom(flag_name) do
    gate = Gate.new(:group, group_name, true)
    {:ok, _flag} = @store.put(flag_name, gate)
    {:ok, true}
  end

  def enable(flag_name, [for_percentage_of: {:time, ratio}]) when is_atom(flag_name) do
    gate = Gate.new(:percentage_of_time, ratio)
    {:ok, _flag} = @store.put(flag_name, gate)
    {:ok, true}
  end

  @doc """
  Disables a feature flag.

  ## Options

  * `:for_actor` - used to disable the flag for a specific term only.
  The value can be any term that implements the `Actor` protocol.
  * `:for_group` - used to disable the flag for a specific group only.
   The value should be a binary or an atom (It's internally converted
  to a binary and it's stored and retrieved as a binary. Atoms are
  supported for retro-compatibility with versions <= 0.9)
  * `:for_percentage_of` - used to disable the flag for a percentage
  of time expressed as `{:time, float}`, where float is in the range
  `0.0 < x < 1.0`.

  ## Examples

  ### Disable globally

      iex> FunWithFlags.enable(:random_koala_gifs)
      iex> FunWithFlags.enabled?(:random_koala_gifs)
      true
      iex> FunWithFlags.disable(:random_koala_gifs)
      {:ok, false}
      iex> FunWithFlags.enabled?(:random_koala_gifs)
      false

  ## Disable for an actor

      iex> FunWithFlags.enable(:spider_sense)
      {:ok, true}
      iex> villain = %{name: "Venom"}
      iex> FunWithFlags.disable(:spider_sense, for_actor: villain)
      {:ok, false}
      iex> FunWithFlags.enabled?(:spider_sense)
      true
      iex> FunWithFlags.enabled?(:spider_sense, for: villain)
      false

  ### Disable for a group

  This example relies on the [reference implementation](https://github.com/tompave/fun_with_flags/blob/master/test/support/test_user.ex)
  used in the tests.

      iex> alias FunWithFlags.TestUser, as: User
      iex> harry = %User{name: "Harry Potter", groups: ["wizards", "gryffindor"]}
      iex> dudley = %User{name: "Dudley Dursley", groups: ["muggles"]}
      iex> FunWithFlags.enable(:hogwarts)
      {:ok, true}
      iex> FunWithFlags.disable(:hogwarts, for_group: "muggles")
      {:ok, false}
      iex> FunWithFlags.enabled?(:hogwarts)
      true
      iex> FunWithFlags.enabled?(:hogwarts, for: harry)
      true
      iex> FunWithFlags.enabled?(:hogwarts, for: dudley)
      false

  ### Disable for a percentage of the time

      iex> FunWithFlags.clear(:random_glitch)
      :ok
      iex> FunWithFlags.disable(:random_glitch, for_percentage_of: {:time, 0.999999999})
      {:ok, false}
      iex> FunWithFlags.enabled?(:random_glitch)
      false
      iex> FunWithFlags.disable(:random_glitch, for_percentage_of: {:time, 0.000000001})
      {:ok, false}
      iex> FunWithFlags.enabled?(:random_glitch)
      true

  """
  @spec disable(atom, options) :: {:ok, false}
  def disable(flag_name, options \\ [])

  def disable(flag_name, []) when is_atom(flag_name) do
    {:ok, flag} = @store.put(flag_name, Gate.new(:boolean, false))
    verify(flag)
  end

  def disable(flag_name, [for_actor: nil]) do
    disable(flag_name)
  end

  def disable(flag_name, [for_actor: actor]) when is_atom(flag_name) do
    gate = Gate.new(:actor, actor, false)
    {:ok, flag} = @store.put(flag_name, gate)
    verify(flag, for: actor)
  end

  def disable(flag_name, [for_group: nil]) do
    disable(flag_name)
  end

  def disable(flag_name, [for_group: group_name]) when is_atom(flag_name) do
    gate = Gate.new(:group, group_name, false)
    {:ok, _flag} = @store.put(flag_name, gate)
    {:ok, false}
  end

  def disable(flag_name, [for_percentage_of: {:time, ratio}])
  when is_atom(flag_name) and is_float(ratio) do
    inverted_ratio = 1.0 - ratio
    {:ok, true} = enable(flag_name, [for_percentage_of: {:time, inverted_ratio}])
    {:ok, false}
  end

  @doc """
  Clears the data of a feature flag.

  Clears the data for an entire feature flag or for a specific
  Actor or Group gate. Clearing a boolean gate is not supported
  because a missing boolean gate is equivalent to a disabled boolean
  gate.

  Sometimes enabling or disabling a gate is not what you want, and you
  need to remove that gate's rules instead. For example, if you don't need
  anymore to explicitly enable or disable a flag for an actor, and the
  default state should be used instead, you'll want to cleare the gate.

  It's also possible to clear the entire flag, by not passing any option.

  ## Options

  * `for_actor: an_actor` - used to clear the flag for a specific term only.
  The value can be any term that implements the `Actor` protocol.
  * `for_group: a_group_name` - used to clear the flag for a specific group only.
   The value should be a binary or an atom (It's internally converted
  to a binary and it's stored and retrieved as a binary. Atoms are
  supported for retro-compatibility with versions <= 0.9)
  * `boolean: true` - used to clear the boolean gate.
  * `for_percentage: true` - used to clear any percentage gate.

  ## Examples

      iex> alias FunWithFlags.TestUser, as: User
      iex> harry = %User{id: 1, name: "Harry Potter", groups: ["wizards", "gryffindor"]}
      iex> hagrid = %User{id: 2, name: "Rubeus Hagrid", groups: ["wizards", "gamekeeper"]}
      iex> dudley = %User{id: 3, name: "Dudley Dursley", groups: ["muggles"]}
      iex> FunWithFlags.disable(:wands)
      iex> FunWithFlags.enable(:wands, for_group: "wizards")
      iex> FunWithFlags.disable(:wands, for_actor: hagrid)
      iex>
      iex> FunWithFlags.enabled?(:wands)
      false
      iex> FunWithFlags.enabled?(:wands, for: harry)
      true
      iex> FunWithFlags.enabled?(:wands, for: hagrid)
      false
      iex> FunWithFlags.enabled?(:wands, for: dudley)
      false
      iex>
      iex> FunWithFlags.clear(:wands, for_actor: hagrid)
      :ok
      iex> FunWithFlags.enabled?(:wands, for: hagrid)
      true
      iex>
      iex> FunWithFlags.clear(:wands)
      :ok
      iex> FunWithFlags.enabled?(:wands)
      false
      iex> FunWithFlags.enabled?(:wands, for: harry)
      false
      iex> FunWithFlags.enabled?(:wands, for: hagrid)
      false
      iex> FunWithFlags.enabled?(:wands, for: dudley)
      false

  """
  @spec clear(atom, options) :: :ok
  def clear(flag_name, options \\ [])

  def clear(flag_name, []) when is_atom(flag_name) do
    {:ok, _flag} = @store.delete(flag_name)
    :ok
  end

  def clear(flag_name, [boolean: true]) do
    gate = Gate.new(:boolean, false) # we only care about the gate id
    _clear_gate(flag_name, gate)
  end

  def clear(flag_name, [for_actor: nil]) do
    clear(flag_name)
  end

  def clear(flag_name, [for_actor: actor]) when is_atom(flag_name) do
    gate = Gate.new(:actor, actor, false) # we only care about the gate id
    _clear_gate(flag_name, gate)
  end

  def clear(flag_name, [for_group: nil]) do
    clear(flag_name)
  end

  def clear(flag_name, [for_group: group_name]) when is_atom(flag_name) do
    gate = Gate.new(:group, group_name, false) # we only care about the gate id
    _clear_gate(flag_name, gate)
  end

  def clear(flag_name, [for_percentage: true]) do
    gate = Gate.new(:percentage_of_time, 0.5) # we only care about the gate id
    _clear_gate(flag_name, gate)
  end

  defp _clear_gate(flag_name, gate) do
    {:ok, _flag} = @store.delete(flag_name, gate)
    :ok
  end

  @doc """
  Returns a list of all flag names currently configured, as atoms.

  This can be useful for debugging or for display purposes,
  but it's not meant to be used at runtime. Undefined flags,
  for example, will be considered disabled.
  """
  @spec all_flag_names() :: {:ok, [atom]} | {:ok, []}
  defdelegate all_flag_names(), to: @store

  @doc """
  Returns a list of all the flags currently configured, as data structures.

  This function is provided for debugging and to build more complex
  functionality (e.g. it's used in the web GUI), but it is not meant to be
  used at runtime to check if a flag is enabled.

  To query the value of a flag, please use the `enabled?2` function instead.
  """
  @spec all_flags() :: {:ok, [FunWithFlags.Flag.t]} | {:ok, []}
  defdelegate all_flags(), to: @store

  @doc """
  Returns a `FunWithFlags.Flag` struct for the given name, or `nil` if
  no flag is found.

  Useful for debugging.
  """
  @spec get_flag(atom) :: FunWithFlags.Flag.t | nil
  def get_flag(name) do
    {:ok, names} = all_flag_names()
    if name in names do
      {:ok, flag} = FunWithFlags.Store.Persistent.adapter.get(name)
      flag
    else
      nil
    end
  end

  defp verify(flag) do
    {:ok, Flag.enabled?(flag)}
  end
  defp verify(flag, [for: data]) do
    {:ok, Flag.enabled?(flag, for: data)}
  end
end


Fix hyperspace bug

Fix hyperspace bug

defmodule Game.Collision do
  @moduledoc """
  Simplistic collision detections.
  Runs as a separate process to avoid slowing game loop in busy screens.
  Tests everything against everything else - no bounding boxes or culling.
  """

  use GenServer

  def start_link(game_pid) do
    GenServer.start_link(__MODULE__, {:ok, game_pid}, [])
  end

  @doc """
  Square a number.
  """
  defmacro sq(n) do
    quote do
      unquote(n) * unquote(n)
    end
  end

  @doc """
  We use points (bullets) inside circles (ships)
  """
  def bullet_hits_ship?(bullet, ship) do
    %{pos: %{x: bx, y: by}} = bullet
    {_ship_id, _tag, sx, sy, sr, _, _} = ship

    sq(bx - sx) + sq(by - sy) < sq(sr)
  end

  @doc """
  Return a tuple of {bullet_id, ship_id} for each collision.
  A single bullet can only hit a single ship
  """
  def detect_bullets_hitting_ships(bullets, ships) do
    l = for b <- bullets, s <- ships, bullet_hits_ship?(b, s), do: {b, s}
    Enum.uniq_by(l, fn {b, _s} -> b.pid end)
  end

  def bullet_hits_asteroid?(bullet, asteroid) do
    %{pos: %{x: bx, y: by}} = bullet
    %{pos: %{x: ax, y: ay}, radius: ar} = asteroid

    sq(bx - ax) + sq(by - ay) < sq(ar)
  end

  @doc """
  Return a tuple of {bullet_id, asteroid_id} for each collision.
  A single bullet can only hit a single asteroid
  """
  def detect_bullets_hitting_asteroids(bullets, asteroids) do
    l = for b <- bullets, a <- asteroids, bullet_hits_asteroid?(b, a), do: {b, a}

    Enum.uniq_by(l, fn {b, _a} -> b.pid end)
  end

  @doc """
  List of bullets (bullet_ids) to stop.
  """
  def unique_bullets(collisions) do
    collisions |> Enum.map(fn {b, _} -> b end) |> Enum.uniq()
  end

  @doc """
  List of targets (ship_ids) to destroy
  """
  def unique_targets(collisions) do
    collisions
    |> Enum.map(fn {_, s} -> s end)
    |> Enum.uniq()
  end

  @doc """
  Test if two circles touch or overlap by comparing
  distances between their centres
  """
  def asteroid_hits_ship?(asteroid, ship) do
    %{pos: %{x: ax, y: ay}, radius: ar} = asteroid
    {_ship_id, _tag, sx, sy, sr, _, _} = ship

    :math.sqrt(sq(ax - sx) + sq(ay - sy)) <= sr + ar
  end

  @doc """
  Return a tuple of {asteroid_id, ship_id} for each collision.
  """
  def detect_asteroids_hitting_ships(asteroids, ships) do
    l = for a <- asteroids, s <- ships, asteroid_hits_ship?(a, s), do: {a, s}
    Enum.uniq_by(l, fn {_a, s} -> s end)
  end

  # GenServer

  def init({:ok, game_pid}) do
    {:ok, game_pid}
  end

  def collision_tests(pid, game) do
    GenServer.cast(pid, {:collision_tests, game})
  end

  # Collisions

  def handle_cast({:collision_tests, game}, game_pid) do
    check_for_collisions(game, game_pid)
    {:noreply, game_pid}
  end

  defp check_for_collisions(game, game_pid) do
    all_asteroids = Map.values(game.state.asteroids)
    # TODO move to game process
    all_bullets = Map.values(game.state.bullets) |> Enum.reject(fn b -> b == :spawn end)
    all_ships = Map.values(game.state.ships)

    bullet_ships = detect_bullets_hitting_ships(all_bullets, all_ships)
    # List of {BulletLoc, Ship Tuple}
    handle_bullets_hitting_ships(game, bullet_ships, game_pid)

    all_asteroids
    |> detect_asteroids_hitting_ships(all_ships)
    |> handle_asteroid_hitting_ships(game_pid)

    bullet_asteroids = detect_bullets_hitting_asteroids(all_bullets, all_asteroids)
    handle_bullets_hitting_asteroids(game, bullet_asteroids, game_pid)

    dud_bullets =
      Enum.uniq(
        unique_bullets(bullet_ships) ++
          unique_bullets(bullet_asteroids)
      )

    stop_bullets(dud_bullets)
  end

  defp handle_asteroid_hitting_ships(asteroid_ships, game_pid) do
    Enum.map(asteroid_ships, fn {a, s} ->
      Game.Server.say_ship_hit_by_asteroid(game_pid, s)
      # TODO send to ship directly
      Game.Server.hyperspace_ship(game_pid, elem(s, 0))
      Game.Server.asteroid_hit(game_pid, a.id)
    end)
  end

  # List of {BulletLoc, Ship Tuple}

  defp handle_bullets_hitting_ships(game, bullet_ships, game_pid) do
    Enum.each(bullet_ships, fn {b, s} ->
      Game.Server.say_player_shot_ship(game_pid, b.id, s)
    end)

    bullet_ships
    |> unique_bullets
    |> Enum.each(fn b ->
      %{pos: %{x: x, y: y}} = game.state.bullets[b.pid]
      Game.Server.explosion(game_pid, x, y)
    end)

    Enum.each(bullet_ships, fn {_, s} ->
      # TODO send this to the ship, not the game
      # TODO this should be the full ship pid
      Game.Server.hyperspace_ship(game_pid, elem(s, 0))
    end)
  end

  defp handle_bullets_hitting_asteroids(_game, bullet_asteroids, game_pid) do
    bullet_asteroids
    |> unique_bullets
    |> Enum.each(fn b -> Game.Server.say_player_shot_asteroid(game_pid, b.id) end)

    bullet_asteroids
    |> unique_targets
    |> Enum.each(fn %{pid: asteroid_pid, pos: %{x: x, y: y}} ->
      Game.Server.explosion(game_pid, x, y)
      Game.Server.asteroid_hit(game_pid, asteroid_pid)
    end)
  end

  defp stop_bullets(bullets) do
    Enum.each(bullets, fn b ->
      Process.exit(b.pid, :normal)
    end)
  end
end

defmodule Game.Collision do
  @moduledoc """
  Simplistic collision detections.
  Runs as a separate process to avoid slowing game loop in busy screens.
  Tests everything against everything else - no bounding boxes or culling.
  """

  use GenServer

  def start_link(game_pid) do
    GenServer.start_link(__MODULE__, {:ok, game_pid}, [])
  end

  @doc """
  Square a number.
  """
  defmacro sq(n) do
    quote do
      unquote(n) * unquote(n)
    end
  end

  @doc """
  We use points (bullets) inside circles (ships)
  """
  def bullet_hits_ship?(bullet, ship) do
    %{pos: %{x: bx, y: by}} = bullet
    {_ship_id, _tag, sx, sy, sr, _, _} = ship

    sq(bx - sx) + sq(by - sy) < sq(sr)
  end

  @doc """
  Return a tuple of {bullet_id, ship_id} for each collision.
  A single bullet can only hit a single ship
  """
  def detect_bullets_hitting_ships(bullets, ships) do
    l = for b <- bullets, s <- ships, bullet_hits_ship?(b, s), do: {b, s}
    Enum.uniq_by(l, fn {b, _s} -> b.pid end)
  end

  def bullet_hits_asteroid?(bullet, asteroid) do
    %{pos: %{x: bx, y: by}} = bullet
    %{pos: %{x: ax, y: ay}, radius: ar} = asteroid

    sq(bx - ax) + sq(by - ay) < sq(ar)
  end

  @doc """
  Return a tuple of {bullet_id, asteroid_id} for each collision.
  A single bullet can only hit a single asteroid
  """
  def detect_bullets_hitting_asteroids(bullets, asteroids) do
    l = for b <- bullets, a <- asteroids, bullet_hits_asteroid?(b, a), do: {b, a}

    Enum.uniq_by(l, fn {b, _a} -> b.pid end)
  end

  @doc """
  List of bullets (bullet_ids) to stop.
  """
  def unique_bullets(collisions) do
    collisions |> Enum.map(fn {b, _} -> b end) |> Enum.uniq()
  end

  @doc """
  List of targets (ship_ids) to destroy
  """
  def unique_targets(collisions) do
    collisions
    |> Enum.map(fn {_, s} -> s end)
    |> Enum.uniq()
  end

  @doc """
  Test if two circles touch or overlap by comparing
  distances between their centres
  """
  def asteroid_hits_ship?(asteroid, ship) do
    %{pos: %{x: ax, y: ay}, radius: ar} = asteroid
    {_ship_id, _tag, sx, sy, sr, _, _} = ship

    :math.sqrt(sq(ax - sx) + sq(ay - sy)) <= sr + ar
  end

  @doc """
  Return a tuple of {asteroid_id, ship_id} for each collision.
  """
  def detect_asteroids_hitting_ships(asteroids, ships) do
    l = for a <- asteroids, s <- ships, asteroid_hits_ship?(a, s), do: {a, s}
    Enum.uniq_by(l, fn {_a, s} -> s end)
  end

  # GenServer

  def init({:ok, game_pid}) do
    {:ok, game_pid}
  end

  def collision_tests(pid, game) do
    GenServer.cast(pid, {:collision_tests, game})
  end

  # Collisions

  def handle_cast({:collision_tests, game}, game_pid) do
    check_for_collisions(game, game_pid)
    {:noreply, game_pid}
  end

  defp check_for_collisions(game, game_pid) do
    all_asteroids = Map.values(game.state.asteroids)
    # TODO move to game process
    all_bullets = Map.values(game.state.bullets) |> Enum.reject(fn b -> b == :spawn end)
    all_ships = Map.values(game.state.ships)

    bullet_ships = detect_bullets_hitting_ships(all_bullets, all_ships)
    # List of {BulletLoc, Ship Tuple}
    handle_bullets_hitting_ships(game, bullet_ships, game_pid)

    all_asteroids
    |> detect_asteroids_hitting_ships(all_ships)
    |> handle_asteroid_hitting_ships(game_pid)

    bullet_asteroids = detect_bullets_hitting_asteroids(all_bullets, all_asteroids)
    handle_bullets_hitting_asteroids(game, bullet_asteroids, game_pid)

    dud_bullets =
      Enum.uniq(
        unique_bullets(bullet_ships) ++
          unique_bullets(bullet_asteroids)
      )

    stop_bullets(dud_bullets)
  end

  defp handle_asteroid_hitting_ships(asteroid_ships, game_pid) do
    Enum.map(asteroid_ships, fn {a, s} ->
      Game.Server.say_ship_hit_by_asteroid(game_pid, s)
      Game.Server.hyperspace_ship(game_pid, s)
      Game.Server.asteroid_hit(game_pid, a.id)
    end)
  end

  # List of {BulletLoc, Ship Tuple}

  defp handle_bullets_hitting_ships(game, bullet_ships, game_pid) do
    Enum.each(bullet_ships, fn {b, s} ->
      Game.Server.say_player_shot_ship(game_pid, b.id, s)
    end)

    bullet_ships
    |> unique_bullets
    |> Enum.each(fn b ->
      %{pos: %{x: x, y: y}} = game.state.bullets[b.pid]
      Game.Server.explosion(game_pid, x, y)
    end)

    Enum.each(bullet_ships, fn {_, s} ->
      # TODO send this to the ship, not the game
      # TODO this should be the full ship pid
      Game.Server.hyperspace_ship(game_pid, elem(s, 0))
    end)
  end

  defp handle_bullets_hitting_asteroids(_game, bullet_asteroids, game_pid) do
    bullet_asteroids
    |> unique_bullets
    |> Enum.each(fn b -> Game.Server.say_player_shot_asteroid(game_pid, b.id) end)

    bullet_asteroids
    |> unique_targets
    |> Enum.each(fn %{pid: asteroid_pid, pos: %{x: x, y: y}} ->
      Game.Server.explosion(game_pid, x, y)
      Game.Server.asteroid_hit(game_pid, asteroid_pid)
    end)
  end

  defp stop_bullets(bullets) do
    Enum.each(bullets, fn b ->
      Process.exit(b.pid, :normal)
    end)
  end
end


interaction between GenServer and GenServerring

defmodule GenServerring do
  use GenServer
  require Crdtex.Set

  defstruct node_set: Crdtex.Set.new, up_set: MapSet.new, payload: nil, counter: 0, callback: nil

  defmacro __using__(_), do: []

  def start_link({name, callback}) do
    {:ok, payload} = callback.init([])
    GenServer.start_link(__MODULE__, {payload, callback}, [{:name, name}])
  end

  def init({payload, callback}) do
    :erlang.send_after(1000,self(),:send_gossip)
    case File.read(ring_path) do
      {:ok,bin} ->
        set = :erlang.binary_to_term(bin)
        monitor(get_set(set))
        {:ok, gen_ring(set, MapSet.new(get_set(set)), payload, 0, callback)}
      _ ->
        set = Crdtex.Set.new
        {:ok, set} = add(set, {node(), 1}, node())
        {:ok, gen_ring(set, MapSet.new([node()]), payload, 1, callback)}
    end
  end

  # payload management, mimicking a GenServer
  def call(server, action), do: GenServer.call(server, action)

  def cast(server, action), do: GenServer.cast(server, action)

  def reply(client, term), do: GenServer.reply(server, term)

  # cluster_management
  # we have to stop all nodes
  def stop({name, n} = server, reason \\ :normal, timeout \\ :infinity) do
    {:ok, ring} = get_ring(server)
    others = ring.up_set |> MapSet.delete(n)
    Enum.each(others, fn(n) -> GenServer.stop({name, n}, reason, timeout) end)
    Genserver.stop(server, reason, timeout)
  end

  def add_node(server, node) when is_binary(node) do
    add_node(server, :"#{node}")
  end
  def add_node(server, node) when is_atom(node) do
    GenServer.cast(server, {:add_node, node})
  end

  def del_node(server, node) when is_binary(node) do
    del_node(server, :"#{node}")
  end
  def del_node(server, node) when is_atom(node) do
    GenServer.cast(server, {:del_node, node})
  end

  def all(server) , do: GenServer.call(server, :get_all)
  def up(server), do: GenServer.call(server, :get_up)

  # classic GenServer callbacks
  def handle_call(:get_all, _, ring) do
    {:reply, get_set(ring.node_set), ring}
  end
  def handle_call(:get_up, _, ring) do
    {:reply, MapSet.to_list(ring.up_set), ring}
  end
  def handle_call(:get_ring, _, ring) do
    {:reply, {:ok, ring}, ring}
  end
  def handle_call(other, from, ring) do
    payload = ring.payload
    up_load = fn(load) -> update_payload(ring, load) end
    case ring.callback.handle_call(other, from, payload) do
      {:reply, reply, new} -> {:reply, reply, up_load.(new)}
      {:reply, reply, new, timeout} -> {:reply, reply, up_load.(new), timeout}
      {:noreply, new} -> {:noreply, up_load.(new)}
      {:noreply, new, timeout} -> {:noreply, up_load.(new), timeout}
      {:stop, reason, reply, new} -> {:stop, reason, reply, up_load.(new)}
      {:stop, reason, new} -> {:stop, reason, up_load.(new)}
    end
  end

  def handle_cast({:reconcile, gossip}, ring) do
    case gossip.from_node do
      [] -> :nothingtodo
      [n] ->
        case MapSet.member?(ring.up_set, n) do
          false -> :nothingtodo
          true ->
            # TODO add this info in the gossip to speedup the convergence of UPs
            Node.monitor(n, true)
        end
    end
    {:noreply, update_ring(ring, gossip)}
  end
  def handle_cast({:add_node, n}, ring) do
    case contain?(ring.node_set, n) do
      true ->
        {:noreply, ring}
      false ->
        new_up_set = MapSet.put(ring.up_set, n)
        {:ok, new_node_set} = add(ring.node_set, {node(), ring.counter + 1}, n)
        {:noreply,
         update_ring(%GenServerring{ring | up_set: new_up_set},
           %{node_set: new_node_set, payload: ring.payload, from_node: []})}
    end
  end
  def handle_cast({:del_node,n}, ring) do
    case contain?(ring.node_set, n) do
      false ->
        {:noreply,ring}
      true ->
        new_up_set = MapSet.delete(ring.up_set, n)
        {:ok, new_node_set} =
          delete(ring.node_set, {node(), ring.counter + 1}, n)
        {:noreply,
         update_ring(%GenServerring{ring | up_set: new_up_set},
           %{node_set: new_node_set, payload: ring.payload, from_node: []})}
    end
  end
  def handle_cast(other, ring) do
    payload = ring.payload
    up_load = fn(load) -> update_payload(ring, load) end
    case ring.callback.handle_cast(other, payload) do
      {:noreply, new} -> {:noreply, up_load.(new)}
      {:noreply, new, timeout} -> {:noreply, up_load.(new), timeout}
      {:stop, reason, new} -> {:stop, reason, up_load.(new)}
    end
  end

  def handle_info(:send_gossip, %GenServerring{node_set: node_set} = ring) do
    :erlang.send_after(1000,self(),:send_gossip)
    if not contain?(node_set, node()) do
      :erlang.send_after(5000, self(), :halt_node)
    end
    case ring.up_set |> MapSet.delete(node()) |> MapSet.to_list do
      [] -> {:noreply,ring}
      active_nodes ->
        {:registered_name, name} = Process.info(self(), :registered_name)
        random_node =
          Enum.at(active_nodes, :random.uniform(length(active_nodes)) - 1)
        GenServer.cast({name, random_node},
          {:reconcile, %{node_set: node_set, payload: ring.payload, from_node: [node()]}})
      {:noreply, ring}
    end
  end
  def handle_info({:nodedown, n}, %GenServerring{up_set: up_set} = ring) do
    {:noreply, %{ring | up_set: MapSet.delete(up_set, n)}}
  end
  def handle_info(:halt_node, s) do
	  # TODO: preserve counter
    File.rm(ring_path)
    :init.stop()
    {:noreply, s}
  end
  def handle_info(other, ring) do
    up_load = fn(load) -> update_payload(ring, load) end
    case ring.callback.handle_info(other, ring.payload) do
      {:noreply, new} -> {:noreply, up_load.(new)}
      {:noreply, new, timeout} -> {:noreply, up_load.(new), timeout}
      {:stop, reason, new} -> {:stop, reason, up_load.(new)}
    end
  end

  # small utilities functions
  defp monitor(list) do
    list = List.delete(list, node())
    Enum.each(list, fn(n) -> Node.monitor(n, :true) end)
  end

  defp gen_ring(set, up, payload, counter, callback) do
    %GenServerring{node_set: set, up_set: up, payload: payload,
      counter: counter, callback: callback}
  end

  defp get_ring(server), do: GenServer.call(server, :get_ring)

  defp update_payload(ring, payload) do
    update_ring(ring,
      %{node_set: ring.node_set, payload: payload, from_node: []})
  end

  defp update_ring(ring, changes) do
    merged_node_set = merge(ring.node_set, changes.node_set)
    merged_payload = merge(ring.payload, changes.payload)
    updated_counter = update_counter(merged_node_set, merged_payload, ring)

    up_set = notify_up_set(get_set(ring.node_set), get_set(merged_node_set),
    MapSet.to_list(ring.up_set) ++ changes.from_node)
    notify_node_set(ring.node_set, merged_node_set, changes.node_set)

    ring =
      gen_ring(merged_node_set, up_set, merged_payload, updated_counter,
        ring.callback)
    notify_payload(value(ring.payload), value(changes.payload), ring)
    ring
  end

  defp update_counter(merged_node_set, merged_payload, old_ring) do
    old_nodes = old_ring.node_set
    old_payload = old_ring.payload
    case {merged_node_set, merged_payload} do
      {^old_nodes, ^old_payload} -> old_ring.counter
      _ -> old_ring.counter + 1
    end
  end

  defp notify_up_set(set, set, old_up) do
    MapSet.new(old_up)
  end
  defp notify_up_set(old_set, merged_set, old_up) do
    new_up = MapSet.difference(MapSet.new(merged_set), MapSet.new(old_set))
    Enum.each(new_up, fn(n) -> Node.monitor(n, :true) end)
    new_set = old_up ++ MapSet.to_list(new_up)
    GenEvent.notify(GenServerring.Events, {:new_up_set, old_up, new_set})
    MapSet.new(new_set)
  end

  defp notify_node_set(set, set, _) do
    :nothingtodo
  end
  defp notify_node_set(old_set, _, new_set) do
    GenEvent.notify(GenServerring.Events, {:new_node_set, get_set(old_set), get_set(new_set)})
      File.write!(ring_path, new_set |> :erlang.term_to_binary)
  end

  defp notify_payload(payload, payload, _) do
    :nothingtodo
  end
  defp notify_payload(_, _, ring) do
    ring.callback.handle_change(ring.payload)
  end

  defp ring_path do
    "#{Application.get_env(:gen_serverring, :data_dir, "./data")}/ring"
  end

  defp value(payload), do: Crdtex.value(payload)

  defp get_set(set), do: Crdtex.value(set)

  defp contain?(set, e), do: Crdtex.value(set, {:contains, e})

  defp add(set, actor, e), do: Crdtex.update(set, actor, {:add, e})

  defp delete(set, actor, e), do: Crdtex.update(set, actor, {:remove, e})

  defp merge(nil, crdt), do: crdt
  defp merge(crdt, crdt), do: crdt
  defp merge(crdt1, crdt2), do: Crdtex.merge(crdt1, crdt2)
end

defmodule GenServerring.App do
  use Application

  def start(type, []) do
    name = Application.get_env(:gen_serverring, :name, :demo_ring)
    callback = Application.get_env(:gen_serverring, :callback, Demo)
    start(type, [{name, callback}])
  end
  def start(_type, args) do
    Supervisor.start_link(GenServerring.App.Sup, args)
  end

  defmodule Sup do
    use Supervisor
    def init(arg) do
      children =
        [worker(:gen_event, [{:local, GenServerring.Events}], id: GenServerring.Events),
         worker(GenServerring, arg)]
      supervise(children, strategy: :one_for_one)
    end
  end
end

defmodule GenServerring do
  use GenServer
  require Crdtex.Set

  defstruct node_set: Crdtex.Set.new, up_set: MapSet.new, payload: nil, counter: 0, callback: nil

  def start_link(__MODULE__, {name, callback}, []) do
    {:ok, payload} = callback.init([])
    GenServer.start_link(__MODULE__, {payload, callback}, [{:name, name}])
  end

  def init({payload, callback}) do
    :erlang.send_after(1000,self(),:send_gossip)
    case File.read(ring_path) do
      {:ok,bin} ->
        set = :erlang.binary_to_term(bin)
        monitor(get_set(set))
        {:ok, gen_ring(set, MapSet.new(get_set(set)), payload, 0, callback)}
      _ ->
        set = Crdtex.Set.new
        {:ok, set} = add(set, {node(), 1}, node())
        {:ok, gen_ring(set, MapSet.new([node()]), payload, 1, callback)}
    end
  end

  # payload management, mimicking a GenServer by using directly the callback
  def call(server, action) do
    case get_ring(server) do
      {:ok, ring} -> do_call(ring, action)
    end
  end

  defp do_call(ring, action) do
    payload = ring.payload
    up_load = fn(load) -> update_payload(ring, load) end
    case ring.callback.handle_call(action, self(), payload) do
      {:reply, reply, new} -> {:reply, reply, up_load.(new)}
      {:reply, reply, new, timeout} -> {:reply, reply, up_load.(new), timeout}
      {:noreply, new} -> {:noreply, up_load.(new)}
      {:noreply, new, timeout} -> {:noreply, up_load.(new), timeout}
      {:stop, reason, reply, new} -> {:stop, reason, reply, up_load.(new)}
      {:stop, reason, new} -> {:stop, reason, up_load.(new)}
    end
  end

  def cast(server, action) do
    case get_ring(server) do
      {:ok, ring} -> do_cast(ring, action)
    end
  end

  defp do_cast(ring, action) do
    payload = ring.payload
    up_load = fn(load) -> update_payload(ring, load) end
    case ring.callback.handle_cast(action, payload) do
      {:noreply, new} -> {:noreply, up_load.(new)}
      {:noreply, new, timeout} -> {:noreply, up_load.(new), timeout}
      {:stop, reason, new} -> {:stop, reason, up_load.(new)}
    end
  end

  def reply(client, term) do
    send(client, term)
    :ok
  end

  # cluster_management
  # we have to stop all nodes
  def stop({name, n} = server, reason \\ :normal, timeout \\ :infinity) do
    {:ok, ring} = get_ring(server)
    others = ring.up_set |> MapSet.delete(n)
    Enum.each(others, fn(n) -> GenServer.stop({name, n}, reason, timeout) end)
    Genserver.stop(server, reason, timeout)
  end

  def add_node(server, node) when is_binary(node) do
    add_node(server, :"#{node}")
  end
  def add_node(server, node) when is_atom(node) do
    GenServer.cast(server, {:add_node, node})
  end

  def del_node(server, node) when is_binary(node) do
    del_node(server, :"#{node}")
  end
  def del_node(server, node) when is_atom(node) do
    GenServer.cast(server, {:del_node, node})
  end

  def all(server) , do: GenServer.call(server, :get_all)
  def up(server), do: GenServer.call(server, :get_up)

  # classic GenServer callbacks
  def handle_call(:get_all, _, ring) do
    {:reply, get_set(ring.node_set), ring}
  end
  def handle_call(:get_up, _, ring) do
    {:reply, MapSet.to_list(ring.up_set), ring}
  end
  def handle_call(:get_ring, _, ring) do
    {:reply, {:ok, ring}, ring}
  end
  def handle_call(other, from, ring) do
    ring.callback.handle_call(other, from, ring.payload)
  end

  def handle_cast({:reconcile, gossip}, ring) do
    case gossip.from_node do
      [] -> :nothingtodo
      [n] ->
        case MapSet.member?(ring.up_set, n) do
          false -> :nothingtodo
          true ->
            # TODO add this info in the gossip to speedup the convergence of UPs
            Node.monitor(n, true)
        end
    end
    {:noreply, update_ring(ring, gossip)}
  end
  def handle_cast({:add_node, n}, ring) do
    case contain?(ring.node_set, n) do
      true ->
        {:noreply, ring}
      false ->
        new_up_set = MapSet.put(ring.up_set, n)
        {:ok, new_node_set} = add(ring.node_set, {node(), ring.counter + 1}, n)
        {:noreply,
         update_ring(%GenServerring{ring | up_set: new_up_set},
           %{node_set: new_node_set, payload: ring.payload, from_node: []})}
    end
  end
  def handle_cast({:del_node,n}, ring) do
    case contain?(ring.node_set, n) do
      false ->
        {:noreply,ring}
      true ->
        new_up_set = MapSet.delete(ring.up_set, n)
        {:ok, new_node_set} =
          delete(ring.node_set, {node(), ring.counter + 1}, n)
        {:noreply,
         update_ring(%GenServerring{ring | up_set: new_up_set},
           %{node_set: new_node_set, payload: ring.payload, from_node: []})}
    end
  end
  def handle_cast(other, ring) do
    ring.callback.handle_cast(other, ring.payload)
  end

  def handle_info(:send_gossip, %GenServerring{node_set: node_set} = ring) do
    :erlang.send_after(1000,self(),:send_gossip)
    if not contain?(node_set, node()) do
      :erlang.send_after(5000, self(), :halt_node)
    end
    case ring.up_set |> MapSet.delete(node()) |> MapSet.to_list do
      [] -> {:noreply,ring}
      active_nodes ->
        random_node =
          Enum.at(active_nodes, :random.uniform(length(active_nodes)) - 1)
        :gen_server.cast({__MODULE__, random_node},
          {:reconcile, %{node_set: node_set, payload: ring.payload, from_node: [node()]}})
      {:noreply, ring}
    end
  end
  def handle_info({:nodedown, n}, %GenServerring{up_set: up_set} = ring) do
    {:noreply, %{ring | up_set: MapSet.delete(up_set, n)}}
  end
  def handle_info(:halt_node, s) do
	  # TODO: preserve counter
    File.rm(ring_path)
    :init.stop()
    {:noreply,s}
  end

  # small utilities functions
  defp monitor(list) do
    list = List.delete(list, node())
    Enum.each(list, fn(n) -> Node.monitor(n, :true) end)
  end

  defp gen_ring(set, up, payload, counter, callback) do
    %GenServerring{node_set: set, up_set: up, payload: payload,
      counter: counter, callback: callback}
  end

  defp get_ring(server), do: GenServer.call(server, :get_ring)

  defp update_payload(ring, payload) do
    update_ring(ring,
      %{node_set: ring.node_set, payload: payload, from_node: []})
  end

  defp update_ring(ring, changes) do
    merged_node_set = merge(ring.node_set, changes.node_set)
    merged_payload = merge(ring.payload, changes.payload)
    updated_counter = update_counter(merged_node_set, merged_payload, ring)

    up_set = notify_up_set(get_set(ring.node_set), get_set(merged_node_set),
    MapSet.to_list(ring.up_set) ++ changes.from_node)
    notify_node_set(ring.node_set, merged_node_set, changes.node_set)
    notify_payload(value(ring.payload), value(changes.payload), changes)

    gen_ring(merged_node_set, up_set, merged_payload, updated_counter,
      ring.callback)
  end

  defp update_counter(merged_node_set, merged_payload, old_ring) do
    old_nodes = old_ring.node_set
    old_payload = old_ring.payload
    case {merged_node_set, merged_payload} do
      {^old_nodes, ^old_payload} -> old_ring.counter
      _ -> old_ring.counter + 1
    end
  end

  defp notify_up_set(set, set, old_up) do
    MapSet.new(old_up)
  end
  defp notify_up_set(old_set, merged_set, old_up) do
    new_up = MapSet.difference(MapSet.new(merged_set), MapSet.new(old_set))
    Enum.each(new_up, fn(n) -> Node.monitor(n, :true) end)
    new_set = old_up ++ MapSet.to_list(new_up)
    GenEvent.notify(GenServerring.Events, {:new_up_set, old_up, new_set})
    MapSet.new(new_set)
  end

  defp notify_node_set(set, set, _) do
    :nothingtodo
  end
  defp notify_node_set(old_set, _, new_set) do
    GenEvent.notify(GenServerring.Events, {:new_node_set, get_set(old_set), get_set(new_set)})
      File.write!(ring_path, new_set |> :erlang.term_to_binary)
  end

  defp notify_payload(payload, payload, _) do
    :nothingtodo
  end
  defp notify_payload(_, _, ring) do
    ring.callback.handle_change(ring.payload)
  end

  defp ring_path do
    "#{Application.get_env(:gen_serverring, :data_dir, "./data")}/ring"
  end

  defp value(payload), do: Crdtex.value(payload)

  defp get_set(set), do: Crdtex.value(set)

  defp contain?(set, e), do: Crdtex.value(set, {:contains, e})

  defp add(set, actor, e), do: Crdtex.update(set, actor, {:add, e})

  defp delete(set, actor, e), do: Crdtex.update(set, actor, {:remove, e})

  defp merge(nil, crdt), do: crdt
  defp merge(crdt, crdt), do: crdt
  defp merge(crdt1, crdt2), do: Crdtex.merge(crdt1, crdt2)

end


fix release task for system libs

fix release task for system libs

defmodule ReleaseManager.Plugin.Nerves do
  use ReleaseManager.Plugin

  @system_libs "/staging/usr/lib/erlang/lib"

  def before_release(%Config{version: version} = config) do
    case System.get_env("NERVES_SYSTEM") do
      nil -> config
      system ->
        info "Modifying release for Nerves System #{Mix.Project.config[:target]}"

        relx_config = config.relx_config

        vm_args_file = 'releases/#{version}/vm.args'
        vm_args =
          relx_config[:overlay]
          |> Enum.find(fn(
            {_, _, ^vm_args_file}) -> true
            _ -> false
          end)
        overlay =
        case vm_args do
          nil ->
            default_vm_args =
              Mix.Project.deps_paths[:nerves]
              |> Path.join("template/_iex.vm.args")
              |> String.to_char_list
            [{:copy, default_vm_args, vm_args_file} | relx_config[:overlay]]
          _ -> relx_config[:overlay]
        end
        relx_config
        |> Enum.reject(fn
          {:include_erts, _} -> true
          {:system_libs, _} -> true
          {:overlay, _} -> true
          _ -> false
        end)
        system_libs = String.to_char_list(Path.join(system, @system_libs))
        relx_config = [{:include_erts, false} | relx_config]
        relx_config = [{:system_libs, system_libs} | relx_config]
        relx_config = [{:overlay, overlay} | relx_config]

        config = %{config | relx_config: relx_config}
    end
  end

  def after_release(_),     do: nil
  def after_package(_),     do: nil
  def after_cleanup(_args), do: nil
end

defmodule ReleaseManager.Plugin.Nerves do
  use ReleaseManager.Plugin

  @system_libs "/staging/usr/lib/erlang/lib"

  def before_release(%Config{version: version} = config) do
    case System.get_env("NERVES_SYSTEM") do
      nil -> config
      system ->
        info "Modifying release for Nerves System #{Mix.Project.config[:target]}"

        relx_config = config.relx_config

        vm_args_file = 'releases/#{version}/vm.args'
        vm_args =
          relx_config[:overlay]
          |> Enum.find(fn(
            {_, _, ^vm_args_file}) -> true
            _ -> false
          end)
        overlay =
        case vm_args do
          nil ->
            default_vm_args =
              Mix.Project.deps_paths[:nerves]
              |> Path.join("template/_iex.vm.args")
              |> String.to_char_list
            [{:copy, default_vm_args, vm_args_file} | relx_config[:overlay]]
          _ -> relx_config[:overlay]
        end
        relx_config
        |> Enum.reject(fn
          {:include_erts, _} -> true
          {:system_libs, _} -> true
          {:overlay, _} -> true
          _ -> false
        end)

        relx_config = [{:include_erts, false} | relx_config]
        relx_config = [{:system_libs, String.to_char_list(system_libs(system))} | relx_config]
        relx_config = [{:overlay, overlay} | relx_config]

        config = %{config | relx_config: relx_config}
    end
  end

  def after_release(_),     do: nil
  def after_package(_),     do: nil
  def after_cleanup(_args), do: nil

  defp system_libs(system), do: @system_libs
end