require "forwardable"

module Flipper
  extend self
  extend Forwardable

  # Private: The namespace for all instrumented events.
  InstrumentationNamespace = :flipper

  # Public: Start here. Given an adapter returns a handy DSL to all the flipper
  # goodness. To see supported options, check out dsl.rb.
  def new(adapter, options = {})
    DSL.new(adapter, options)
  end

  # Public: Configure flipper.
  #
  #   Flipper.configure do |config|
  #     config.adapter { ... }
  #   end
  #
  # Yields Flipper::Configuration instance.
  def configure
    yield configuration if block_given?
  end

  # Public: Returns Flipper::Configuration instance.
  def configuration
    @configuration ||= Configuration.new
  end

  # Public: Sets Flipper::Configuration instance.
  def configuration=(configuration)
    # need to reset flipper instance if configuration changes
    self.instance = nil
    @configuration = configuration
  end

  # Public: Default per thread flipper instance if configured. You should not
  # need to use this directly as most of the Flipper::DSL methods are delegated
  # from Flipper module itself. Instead of doing Flipper.instance.enabled?(:search),
  # you can use Flipper.enabled?(:search) for the same result.
  #
  # Returns Flipper::DSL instance.
  def instance
    Thread.current[:flipper_instance] ||= configuration.default
  end

  # Public: Set the flipper instance. It is most common to use the
  # Configuration#default to set this instance, but for things like the test
  # environment, this writer is actually useful.
  def instance=(flipper)
    Thread.current[:flipper_instance] = flipper
  end

  # Public: All the methods delegated to instance. These should match the
  # interface of Flipper::DSL.
  def_delegators :instance,
                 :enabled?, :enable, :disable,
                 :enable_expression, :disable_expression,
                 :expression, :add_expression, :remove_expression,
                 :enable_actor, :disable_actor,
                 :enable_group, :disable_group,
                 :enable_percentage_of_actors, :disable_percentage_of_actors,
                 :enable_percentage_of_time, :disable_percentage_of_time,
                 :features, :feature, :[], :preload, :preload_all,
                 :adapter, :adapter_stack, :add, :exist?, :remove, :import, :export,
                 :memoize=, :memoizing?, :read_only?,
                 :sync, :sync_secret # For Flipper::Cloud. Will error for OSS Flipper.

  def any(*args)
    Expression.build({ Any: args.flatten })
  end

  def all(*args)
    Expression.build({ All: args.flatten })
  end

  def constant(value)
    Expression.build(value)
  end

  def property(name)
    Expression.build({ Property: name })
  end

  def string(value)
    Expression.build({ String: value })
  end

  def number(value)
    Expression.build({ Number: value })
  end

  def boolean(value)
    Expression.build({ Boolean: value })
  end

  def random(max)
    Expression.build({ Random: max })
  end

  def now
    Expression.build({ Now: [] })
  end

  def time(value)
    Expression.build({ Time: value })
  end

  def feature_enabled(name)
    Expression.build({ FeatureEnabled: name })
  end

  def feature_disabled(name)
    feature_enabled(name).eq(false)
  end

  # Public: Use this to register a group by name.
  #
  # name - The Symbol name of the group.
  # block - The block that should be used to determine if the group matches a
  #         given actor.
  #
  # Examples
  #
  #   Flipper.register(:admins) { |actor|
  #     actor.respond_to?(:admin?) && actor.admin?
  #   }
  #
  # Returns a Flipper::Group.
  # Raises Flipper::DuplicateGroup if the group is already registered.
  def register(name, &block)
    group = Types::Group.new(name, &block)
    groups_registry.add(group.name, group)
    group
  rescue Registry::DuplicateKey
    raise DuplicateGroup, "Group #{name.inspect} has already been registered"
  end

  # Public: Returns a Set of registered Types::Group instances.
  def groups
    groups_registry.values.to_set
  end

  # Public: Returns a Set of symbols where each symbol is a registered
  # group name. If you just want the names, this is more efficient than doing
  # `Flipper.groups.map(&:name)`.
  def group_names
    groups_registry.keys.to_set
  end

  # Public: Clears the group registry.
  #
  # Returns nothing.
  def unregister_groups
    groups_registry.clear
  end

  # Public: Check if a group exists
  #
  # Returns boolean
  def group_exists?(name)
    groups_registry.key?(name)
  end

  # Public: Fetches a group by name.
  #
  # name - The Symbol name of the group.
  #
  # Examples
  #
  #   Flipper.group(:admins)
  #
  # Returns Flipper::Group.
  def group(name)
    groups_registry.get(name) || Types::Group.new(name)
  end

  # Internal: Registry of all groups_registry.
  def groups_registry
    @groups_registry ||= Registry.new
  end

  # Internal: Change the groups_registry registry.
  def groups_registry=(registry)
    @groups_registry = registry
  end
end

require 'flipper/actor'
require 'flipper/adapter'
require 'flipper/adapters/wrapper'
require 'flipper/adapters/actor_limit'
require 'flipper/adapters/instrumented'
require 'flipper/adapters/memoizable'
require 'flipper/adapters/memory'
require 'flipper/adapters/strict'
require 'flipper/adapter_builder'
require 'flipper/configuration'
require 'flipper/dsl'
require 'flipper/errors'
require 'flipper/feature'
require 'flipper/gate'
require 'flipper/instrumenters/memory'
require 'flipper/instrumenters/noop'
require 'flipper/identifier'
require 'flipper/middleware/memoizer'
require 'flipper/middleware/setup_env'
require 'flipper/poller'
require 'flipper/registry'
require 'flipper/expression'
require 'flipper/type'
require 'flipper/types/actor'
require 'flipper/types/boolean'
require 'flipper/types/group'
require 'flipper/types/percentage'
require 'flipper/types/percentage_of_actors'
require 'flipper/types/percentage_of_time'
require 'flipper/typecast'
require 'flipper/version'

require "flipper/engine" if defined?(Rails)
