require 'singleton'
require 'hash_recursive_merge'

module SoraGeocoding
  class ConfigurationHash < Hash
    include HashRecursiveMerge

    def method_missing(method, *args, &block)
      key?(method) ? self[method] : super
    end

    def respond_to_missing?(method, include_private)
      key?(method) ? true : super
    end
  end

  #
  # Configuration options should be set by passing a hash:
  #
  #   SoraGeocoding.configure(
  #     :timeout     => 5,
  #     :site        => 'yahoo',
  #     :yahoo_appid => '2a9fsa983jaslfj982fjasd'
  #   )
  #
  def self.configure(options = nil)
    options ? Configuration.instance.configure(options) : config
  end

  #
  # Read-only access to the singleton's config data.
  #
  def self.config
    Configuration.instance.data
  end

  #
  # Read-only access to specific config data.
  #
  def self.config_for_lookup(lookup_name)
    data = config.clone
    data.select! { |key, _value| Configuration::OPTIONS.include?(key) }
    data.merge!(config[lookup_name]) if config.key?(lookup_name)
    data
  end

  class Configuration
    include Singleton

    OPTIONS = %i[
      timeout
      http_headers
      use_https
      http_proxy
      https_proxy
      basic_auth
      site
      yahoo_appid
      logger_level
      always_raise
    ].freeze

    attr_accessor :data

    def self.set_defaults
      instance.set_defaults
    end

    OPTIONS.each do |o|
      define_method o do
        @data[o]
      end
      define_method "#{o}=" do |value|
        @data[o] = value
      end
    end

    def configure(options)
      @data.rmerge!(options)
    end

    def initialize
      @data = SoraGeocoding::ConfigurationHash.new
      set_defaults
    end

    def set_defaults
      # options
      @data[:timeout]      = 3           # timeout (secs)
      @data[:http_headers] = {}          # HTTP headers
      @data[:use_https]    = false       # use HTTPS for requests?
      @data[:http_proxy]   = nil         # HTTP proxy server (user:pass@host:port)
      @data[:https_proxy]  = nil         # HTTPS proxy server (user:pass@host:port)
      @data[:basic_auth]   = {}          # user and password for basic auth ({:user => "user", :password => "password"})
      @data[:site]         = nil
      @data[:yahoo_appid]  = nil         # API key for Yahoo Geocoder API
      @data[:logger_level] = ::Logger::WARN # log level, if kernel logger is used

      # [supports]
      # - :all
      # - SocketError
      # - Timeout::Error
      @data[:always_raise] = []
    end

    instance_eval(OPTIONS.map do |option|
      o = option.to_s
      <<-INSTANCE_DATA
      def #{o}
        instance.data[:#{o}]
      end

      def #{o}=(value)
        instance.data[:#{o}] = value
      end
      INSTANCE_DATA
    end.join("\n\n"))
  end
end