begin
  require 'fastthread'
rescue LoadError
end

module DataObjects
  # An abstract connection to a DataObjects resource. The physical connection may be broken and re-established from time to time.
  class Connection

    include Logging

    # Make a connection to the database using the DataObjects::URI given.
    # Note that the physical connection may be delayed until the first command is issued, so success here doesn't necessarily mean you can connect.
    def self.new(uri_s)
      uri = DataObjects::URI::parse(uri_s)

      case uri.scheme.to_sym
      when :java
        warn 'JNDI URLs (connection strings) are only for use with JRuby' unless RUBY_PLATFORM =~ /java/

        driver   = uri.query.delete('scheme')
        driver   = uri.query.delete('driver')

        conn_uri = uri.to_s.gsub(/\?$/, '')
      when :jdbc
        warn 'JDBC URLs (connection strings) are only for use with JRuby' unless RUBY_PLATFORM =~ /java/

        path = uri.subscheme
        driver = if path.split(':').first == 'sqlite'
          'sqlite3'
        elsif path.split(':').first == 'postgresql'
          'postgres'
        else
          path.split(':').first
        end

        conn_uri = uri_s # NOTE: for now, do not reformat this JDBC connection
                         # string -- or, in other words, do not let
                         # DataObjects::URI#to_s be called -- as it is not
                         # correctly handling JDBC URLs, and in doing so, causing
                         # java.sql.DriverManager.getConnection to throw a
                         # 'No suitable driver found for...' exception.
      else
        driver   = uri.scheme
        conn_uri = uri
      end

      # Exceptions to how a driver class is determined for a given URI
      driver_class = if driver == 'sqlserver'
        'SqlServer'
      else
        driver.capitalize
      end

      clazz = DataObjects.const_get(driver_class)::Connection
      unless clazz.method_defined? :close
        if (uri.scheme.to_sym == :java)
          clazz.class_eval do
            alias close dispose
          end
        else
          clazz.class_eval do
            include Pooling
            alias close release
          end
        end
      end
      clazz.new(conn_uri)
    end

    # Ensure that all Connection subclasses handle pooling and logging uniformly.
    # See also DataObjects::Pooling and DataObjects::Logger
    def self.inherited(target)
      target.class_eval do

        # Allocate a Connection object from the pool, creating one if necessary. This method is active in Connection subclasses only.
        def self.new(*args)
          instance = allocate
          instance.send(:initialize, *args)
          instance
        end

        include Quoting
      end

      if driver_module_name = target.name.split('::')[-2]
        driver_module = DataObjects::const_get(driver_module_name)
        driver_module.class_eval <<-EOS, __FILE__, __LINE__
          def self.logger
            @logger
          end

          def self.logger=(logger)
            @logger = logger
          end
        EOS

        driver_module.logger = DataObjects::Logger.new(nil, :off)
      end
    end

    #####################################################
    # Standard API Definition
    #####################################################

    # Show the URI for this connection, without
    # the password the connection was setup with
    def to_s
      @uri.to_s
    end

    def initialize(uri) #:nodoc:
      raise NotImplementedError.new
    end

    def dispose #:nodoc:
      raise NotImplementedError.new
    end

    # Create a Command object of the right subclass using the given text
    def create_command(text)
      concrete_command.new(self, text)
    end

    def extension
      driver_namespace.const_get('Extension').new(self)
    end

    private

    def driver_namespace
      DataObjects::const_get(self.class.name.split('::')[-2])
    end

    def concrete_command
      @concrete_command || begin

        class << self
          private
          def concrete_command
            @concrete_command
          end
        end

        @concrete_command = DataObjects::const_get(self.class.name.split('::')[-2]).const_get('Command')
      end
    end

  end
end
