module Aviator
module Openstack

  #
  # <b>Request Options</b>
  #
  # The following options may be used in combination with each other when calling
  # an OpenStack request class
  #
  # :api_version => :v2::
  #     Forces Aviator to use the request class for the v2 API. For any other
  #     version, replace :v2 with the desired one. Note that this may throw an
  #     error if no such request class for the given api version exists. If you
  #     want to globally specify the API version to use for a specific service,
  #     declare it in your config file under the correct environment. For example:
  #
  #  production:
  #    provider: openstack
  #    ...
  #    compute_service:
  #       api_version: v2
  #
  # Note that the <tt>:api_version</tt> option overrides whatever is declared in the
  # configuration.
  #
  # :endpoint_type => (:public|:admin)::
  #    This allows you to be specific about the endpoint type in cases where two
  #    request classes under admin and public endpoints of the same service share
  #    the same name. This is true, for example, for the :list_tenants request of
  #    the identity service's v2 API. Its public endpoint will return only the tenants
  #    the user is a member of whereas the admin endpoint will return all tenants
  #    in the system.
  #
  # :session_data => Hash::
  #    Under normal situations, you wouldn't need to use this as it is automatically populated
  #    by the Session object provided it is authenticated. The specific use case when you'd
  #    need to set thsi optin is when you want to use Aviator to seed your OpenStack installation.
  #    In such a scenario, you would need to use a service token since no usernames and tenants
  #    would exist yet in the environment. To use a service token with Aviator, you will need to
  #    write something similar to the following example:
  #
  #  openstack = Aviator::Session.new(:config => { :provider => 'openstack'})
  #
  #  session_data = {:base_url      => 'http://example.com',
  #                  :service_token => 'service-token-created-at-openstack-install-time'}
  #
  #  openstack.request :identity, :create_tenant, :api_version => :v2, :session_data => session_data) do |params|
  #    params.name        = 'Tenant A'
  #    params.description = 'First Tenant!'
  #    params.enabled     = true
  #  end
  #
  # Notice how the above code skips authentication. This is because the service token is
  # pre-validated and ready for use with any request. Also note how we're providing a <tt>:base_url</tt>
  # member in our session data. This is necessary since we normally get the service endpoints from
  # Keystone when we authenticate. Now since we are not authenticating against Keystone, we don't have
  # that catalogue to begin with. Thus the need to hardcode it in the request.
  #
  module Provider

    class MultipleServiceApisError < StandardError
      def initialize(service, entries, request_name)
        types = entries.map{|e| e[:type] }.join("\n - ")
        msg = <<EOF
Multiple entries for the #{ service } service were found in the api catalog:

 - #{ types }

I'm unable to guess which one it is you want to use. To fix this problem, you'll need to
do one of two things:

  1) Indicate in the config file the api version you want to use:

      production:
        provider: openstack
        ...
        #{ service }_service:
          api_version: v2

  2) Indicate the api version when you call the request:

      session.#{ service }_service.request :#{ request_name }, :api_version => :v2 { ... }

If you combine the two methods, method #2 will override method #1

EOF
        super(msg)
      end
    end

    class << self

      #:nodoc:
      def find_request(service, name, session_data, options)
        service = service.to_s
        endpoint_type = options[:endpoint_type]
        endpoint_types = if endpoint_type
                           [StrUtil.camelize(endpoint_type.to_s)]
                         else
                           ['Public', 'Admin']
                         end

        namespace = Aviator.const_get('Openstack') \
                           .const_get(StrUtil.camelize(service)) \
                           .const_get('Requests')

        if options[:api_version]
          m = options[:api_version].to_s.match(/(v\d+)\.?\d*/)
          version = StrUtil.camelize(m[1].to_s) unless m.nil?
        end

        version ||= infer_version(session_data, name, service)

        unless version.nil?
          version = StrUtil.camelize(version.to_s)
        end

        return nil unless version && namespace.const_defined?(version)

        namespace = namespace.const_get(version, name)

        endpoint_types.each do |endpoint_type|
          name = StrUtil.camelize(name.to_s)

          next unless namespace.const_defined?(endpoint_type)
          next unless namespace.const_get(endpoint_type).const_defined?(name)

          return namespace.const_get(endpoint_type).const_get(name)
        end

        nil
      end


      def root_dir
        Pathname.new(__FILE__).join('..').expand_path
      end


      def request_file_paths(service)
          Dir.glob(Pathname.new(__FILE__).join(
            '..',
             service.to_s,
            'requests',
            '**',
            '*.rb'
            ).expand_path
          )
      end


      private

      def infer_version(session_data, request_name, service)
        if session_data.has_key?(:auth_service) && session_data[:auth_service][:api_version]
        session_data[:auth_service][:api_version].to_sym

        elsif session_data.has_key?(:auth_service) && session_data[:auth_service][:host_uri]
          m = session_data[:auth_service][:host_uri].match(/(v\d+)\.?\d*/)
          return m[1].to_sym unless m.nil?

        elsif session_data.has_key? :base_url
          m = session_data[:base_url].match(/(v\d+)\.?\d*/)
          return m[1].to_sym unless m.nil?

        elsif session_data.has_key?(:body) && session_data[:body].has_key?(:access)
          service_specs = session_data[:body][:access][:serviceCatalog].select{|s| s[:type].match("#{ service }(v\d+)?") }
          raise MultipleServiceApisError.new(service, service_specs, request_name) unless service_specs.length <= 1
          raise Aviator::Service::MissingServiceEndpointError.new(service.to_s, request_name) unless service_specs.length > 0
          version = service_specs[0][:endpoints][0][:publicURL].match(/(v\d+)\.?\d*/)
          version ? version[1].to_sym : :v1

        elsif session_data.has_key?(:headers) && session_data[:headers].has_key?("x-subject-token")
          service_specs = session_data[:body][:token][:catalog].select{|s| s[:type].match("#{ service }(v\d+)?") }
          raise MultipleServiceApisError.new(service, service_specs, request_name) unless service_specs.length <= 1
          raise Aviator::Service::MissingServiceEndpointError.new(service.to_s, request_name) unless service_specs.length > 0
          version = service_specs[0][:endpoints][0][:url].match(/(v\d+)\.?\d*/)
          version ? version[1].to_sym : :v1
        end
      end

    end # class << self

  end # module Provider

end # module Openstack
end # module Aviator
