module Vanity

  # Vanity.playground.configuration
  class Configuration
  end

  # Playground catalogs all your experiments, holds the Vanity configuration.
  # For example:
  #   Vanity.playground.logger = my_logger
  #   puts Vanity.playground.map(&:name)
  class Playground

    # Created new Playground. Unless you need to, use the global Vanity.playground.
    def initialize
      @experiments = {}
      @host, @port, @db = "127.0.0.1", 6379, 0
      @namespace = "vanity:#{Vanity::Version::MAJOR}"
      @load_path = "experiments"
    end
    
    # Redis host name.  Default is 127.0.0.1
    attr_accessor :host

    # Redis port number.  Default is 6379.
    attr_accessor :port

    # Redis database number. Default is 0.
    attr_accessor :db

    # Redis database password.
    attr_accessor :password

    # Namespace for database keys.  Default is vanity:n, where n is the major release number, e.g. vanity:1 for 1.0.3.
    attr_accessor :namespace

    # Path to load experiment files from.
    attr_accessor :load_path

    # Logger.
    attr_accessor :logger

    # Defines a new experiment. Generally, do not call this directly,
    # use #experiment instead.
    def define(name, options = nil, &block)
      id = name.to_s.downcase.gsub(/\W/, "_")
      raise "Experiment #{id} already defined once" if @experiments[id]
      options ||= {}
      type = options[:type] || :ab_test
      klass = Experiment.const_get(type.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase })
      experiment = klass.new(self, id, name)
      experiment.instance_eval &block
      experiment.save
      @experiments[id] = experiment
    end

    # Returns the named experiment. You may not have guessed, but this method
    # raises an exception if it cannot load the experiment's definition.
    #
    # Experiment names are always mapped by downcasing them and replacing
    # non-word characters with underscores, so "Green call to action" becomes
    # "green_call_to_action". You can also use a symbol if you feel like it.
    def experiment(name)
      id = name.to_s.downcase.gsub(/\W/, "_")
      unless @experiments.has_key?(id)
        require File.join(load_path, id)
      end
      @experiments[id] or fail LoadError, "Expected experiments/#{id}.rb to define experiment #{name}"
    end

    # Returns list of all loaded experiments.
    def experiments
      Dir[File.join(load_path, "*.rb")].each do |file|
        require file
      end
      @experiments.values
    end

    # Use this instance to access the Redis database.
    def redis
      redis = Redis.new(host: self.host, port: self.port, db: self.db,
                        password: self.password, logger: self.logger)
      class << self ; self ; end.send(:define_method, :redis) { redis }
      redis
    end

  end

  @playground = Playground.new
  class << self

    # Returns the playground instance.
    def playground
      @playground
    end

    # Returns the Vanity context.  For example, when using Rails this would be
    # the current controller, which can be used to get/set the vanity identity.
    def context
      Thread.current[:vanity_context]
    end

    # Sets the Vanity context.  For example, when using Rails this would be
    # set by the set_vanity_context before filter (via use_vanity).
    def context=(context)
      Thread.current[:vanity_context] = context
    end

    # Path to template.
    def template(name)
      path = File.join(File.dirname(__FILE__), "templates/#{name}")
      path << ".erb" unless name["."]
      path
    end

  end
end

class Object

  # Use this method to define or access an experiment.
  # 
  # To define an experiment, call with a name, options and a block.  For
  # example:
  #   experiment "Text size" do
  #     alternatives :small, :medium, :large
  #   end
  #
  #   puts experiment(:text_size).alternatives
  def experiment(name, options = nil, &block)
    if block
      Vanity.playground.define(name, options, &block)
    else
      Vanity.playground.experiment(name)
    end
  end
end