module DataMapper
  module Adapters
    extend Chainable
    extend DataMapper::Assertions

    # Set up an adapter for a storage engine
    #
    # @see DataMapper.setup
    #
    # @api private
    def self.new(repository_name, options)
      options = normalize_options(options)

      case options.fetch(:adapter)
      when 'java'      
        # discover the real adapter
        jndi_uri = "#{options[:scheme]}:#{options[:path]}"
        context = javax.naming.InitialContext.new 
        ds= context.lookup(jndi_uri)
        conn = ds.getConnection
        begin
          metadata = conn.getMetaData
          driver_name = metadata.getDriverName

          driver = case driver_name
            when /mysql/i  then 'mysql'
            when /oracle/i then 'oracle'
            when /postgres/i then 'postgres'
            when /sqlite/i then 'sqlite'
            when /sqlserver|tds|Microsoft SQL/i then 'sqlserver'
            else
              nil # not supported
            end # case
            options[:adapter] = driver
        ensure
          conn.close
        end
      else
        driver = options.fetch(:adapter)
      end # case 

      adapter_class(driver).new(repository_name, options)
    end

    # The path used to require the in memory adapter
    #
    # Set this if you want to register your own adapter
    # to be used when you specify an 'in_memory' connection
    # during
    #
    # @see DataMapper.setup
    #
    # @param [String] path
    #   the path used to require the desired in memory adapter
    #
    # @api semipublic
    def self.in_memory_adapter_path=(path)
      @in_memory_adapter_path = path
    end

    # The path used to require the in memory adapter
    #
    # @see DataMapper.setup
    #
    # @return [String]
    #   the path used to require the desired in memory adapter
    #
    # @api semipublic
    def self.in_memory_adapter_path
      @in_memory_adapter_path ||= 'dm-core/adapters/in_memory_adapter'
    end

    class << self
      private

      # Normalize the arguments passed to new()
      #
      # Turns options hash or connection URI into the options hash used
      # by the adapter.
      #
      # @param [Hash, Addressable::URI, String] options
      #   the options to be normalized
      #
      # @return [Mash]
      #   the options normalized as a Mash
      #
      # @api private
      def normalize_options(options)
        case options
          when Hash             then normalize_options_hash(options)
          when Addressable::URI then normalize_options_uri(options)
          when String           then normalize_options_string(options)
          else
            assert_kind_of 'options', options, Hash, Addressable::URI, String
        end
      end

      # Normalize Hash options into a Mash
      #
      # @param [Hash] hash
      #   the hash to be normalized
      #
      # @return [Mash]
      #   the options normalized as a Mash
      #
      # @api private
      def normalize_options_hash(hash)
        DataMapper::Ext::Hash.to_mash(hash)
      end

      # Normalize Addressable::URI options into a Mash
      #
      # @param [Addressable::URI] uri
      #   the uri to be normalized
      #
      # @return [Mash]
      #   the options normalized as a Mash
      #
      # @api private
      def normalize_options_uri(uri)
        options = normalize_options_hash(uri.to_hash)

        # Extract the name/value pairs from the query portion of the
        # connection uri, and set them as options directly.
        if options.fetch(:query)
          options.update(uri.query_values)
        end

        options[:adapter] = options.fetch(:scheme)

        options
      end

      # Normalize String options into a Mash
      #
      # @param [String] string
      #   the string to be normalized
      #
      # @return [Mash]
      #   the options normalized as a Mash
      #
      # @api private
      def normalize_options_string(string)
        normalize_options_uri(Addressable::URI.parse(string))
      end

      # Return the adapter class constant
      #
      # @example
      #   DataMapper::Adapters.send(:adapter_class, 'mysql') # => DataMapper::Adapters::MysqlAdapter
      #
      # @param [Symbol] name
      #   the name of the adapter
      #
      # @return [Class]
      #   the AbstractAdapter subclass
      #
      # @api private
      def adapter_class(name)
        adapter_name = normalize_adapter_name(name)
        class_name = (DataMapper::Inflector.camelize(adapter_name) << 'Adapter').to_sym
        load_adapter(adapter_name) unless const_defined?(class_name)
        const_get(class_name)
      end

      # Return the name of the adapter
      #
      # @example
      #   DataMapper::Adapters.adapter_name('MysqlAdapter') # => 'mysql'
      #
      # @param [String] const_name
      #   the adapter constant name
      #
      # @return [String]
      #   the name of the adapter
      #
      # @api semipublic
      def adapter_name(const_name)
        const_name.to_s.chomp('Adapter').downcase
      end

      # Require the adapter library
      #
      # @param [String, Symbol] name
      #   the name of the adapter
      #
      # @return [Boolean]
      #   true if the adapter is loaded
      #
      # @api private
      def load_adapter(name)
        require "dm-#{name}-adapter"
      rescue LoadError => original_error
        begin
          require in_memory_adapter?(name) ? in_memory_adapter_path : legacy_path(name)
        rescue LoadError
          raise original_error
        end
      end

      # Returns wether or not the given adapter name is considered an in memory adapter
      #
      # @param [String, Symbol] name
      #   the name of the adapter
      #
      # @return [Boolean]
      #   true if the adapter is considered to be an in memory adapter
      #
      # @api private
      def in_memory_adapter?(name)
        name.to_s == 'in_memory'
      end

      # Returns the fallback filename that would be used to require the named adapter
      #
      # The fallback format is "#{name}_adapter" and will be phased out in favor of
      # the properly 'namespaced' "dm-#{name}-adapter" format.
      #
      # @param [String, Symbol] name
      #   the name of the adapter to require
      #
      # @return [String]
      #   the filename that gets required for the adapter identified by name
      #
      # @api private
      def legacy_path(name)
        "#{name}_adapter"
      end

      # Adjust the adapter name to match the name used in the gem providing the adapter
      #
      # @param [String, Symbol] name
      #   the name of the adapter
      #
      # @return [String]
      #   the normalized adapter name
      #
      # @api private
      def normalize_adapter_name(name)
        case (original = name.to_s)
        when 'sqlite3'
          'sqlite'
        when 'inmemory' # Addressable >= v2.4 does not support underscores in scheme
          'in_memory'
        else
          original
        end
      end

    end

    extendable do
      # @api private
      def const_added(const_name)
      end
    end
  end # module Adapters
end # module DataMapper
