%w'bigdecimal date thread time uri'.each{|f| require f}

# Top level module for Sequel
#
# There are some class methods that are added via metaprogramming, one for
# each supported adapter.  For example:
#
#   DB = Sequel.sqlite # Memory database
#   DB = Sequel.sqlite('blog.db')
#   DB = Sequel.postgres('database_name', :user=>'user', 
#          :password=>'password', :host=>'host', :port=>5432, 
#          :max_connections=>10)
#
# If a block is given to these methods, it is passed the opened Database
# object, which is closed (disconnected) when the block exits, just
# like a block passed to connect.  For example:
#
#   Sequel.sqlite('blog.db'){|db| puts db[:users].count} 
#
# Sequel converts two digit years in Dates and DateTimes by default,
# so 01/02/03 is interpreted at January 2nd, 2003, and 12/13/99 is interpreted
# as December 13, 1999. You can override this to treat those dates as
# January 2nd, 0003 and December 13, 0099, respectively, by setting: 
#
#   Sequel.convert_two_digit_years = false
#
# Sequel can use either Time or DateTime for times returned from the
# database.  It defaults to Time.  To change it to DateTime, use:
#
#   Sequel.datetime_class = DateTime
#
# Sequel doesn't pay much attention to timezones by default, but you can set it
# handle timezones if you want.  There are three separate timezone settings:
#
# * application_timezone - The timezone you want the application to use.  This is
#   the timezone that incoming times from the database and typecasting are converted
#   to.
# * database_timezone - The timezone for storage in the database.  This is the
#   timezone to which Sequel will convert timestamps before literalizing them
#   for storage in the database.  It is also the timezone that Sequel will assume
#   database timestamp values are already in (if they don't include an offset).
# * typecast_timezone - The timezone that incoming data that Sequel needs to typecast
#   is assumed to be already in (if they don't include an offset).
#
# You can set also set all three timezones to the same value at once via
# Sequel.default_timezone=.
#
#   Sequel.application_timezone = :utc # or :local or nil
#   Sequel.database_timezone = :utc # or :local or nil
#   Sequel.typecast_timezone = :utc # or :local or nil
#   Sequel.default_timezone = :utc # or :local or nil
#
# The only timezone values that are supported by default are :utc (convert to UTC),
# :local (convert to local time), and nil (don't convert).  If you need to
# convert to a specific timezone, or need the timezones being used to change based
# on the environment (e.g. current user), you need to use an extension (and use
# DateTime as the datetime_class).
#
# You can set the SEQUEL_NO_CORE_EXTENSIONS constant or environment variable to have
# Sequel not extend the core classes.
module Sequel
  @convert_two_digit_years = true
  @datetime_class = Time
  @virtual_row_instance_eval = true
  
  class << self
    attr_accessor :convert_two_digit_years, :datetime_class, :virtual_row_instance_eval
  end

  # Returns true if the passed object could be a specifier of conditions, false otherwise.
  # Currently, Sequel considers hashes and arrays of all two pairs as
  # condition specifiers.
  def self.condition_specifier?(obj)
    case obj
    when Hash
      true
    when Array
      !obj.empty? && obj.all?{|i| (Array === i) && (i.length == 2)}
    else
      false
    end
  end

  # Creates a new database object based on the supplied connection string
  # and optional arguments.  The specified scheme determines the database
  # class used, and the rest of the string specifies the connection options.
  # For example:
  #
  #   DB = Sequel.connect('sqlite:/') # Memory database
  #   DB = Sequel.connect('sqlite://blog.db') # ./blog.db
  #   DB = Sequel.connect('sqlite:///blog.db') # /blog.db
  #   DB = Sequel.connect('postgres://user:password@host:port/database_name')
  #   DB = Sequel.connect('sqlite:///blog.db', :max_connections=>10)
  #
  # If a block is given, it is passed the opened Database object, which is
  # closed when the block exits.  For example:
  #
  #   Sequel.connect('sqlite://blog.db'){|db| puts db[:users].count}  
  def self.connect(*args, &block)
    Database.connect(*args, &block)
  end
  
  # Convert the exception to the given class.  The given class should be
  # Sequel::Error or a subclass.  Returns an instance of klass with
  # the message and backtrace of exception.
  def self.convert_exception_class(exception, klass)
    return exception if exception.is_a?(klass)
    e = klass.new("#{exception.class}: #{exception.message}")
    e.wrapped_exception = exception
    e.set_backtrace(exception.backtrace)
    e
  end

  # Load all Sequel extensions given.  Only loads extensions included in this
  # release of Sequel, doesn't load external extensions.
  #
  #   Sequel.extension(:schema_dumper)
  #   Sequel.extension(:pagination, :query)
  def self.extension(*extensions)
    require(extensions, 'extensions')
  end
  
  # Set the method to call on identifiers going into the database.  This affects
  # the literalization of identifiers by calling this method on them before they are input.
  # Sequel upcases identifiers in all SQL strings for most databases, so to turn that off:
  #
  #   Sequel.identifier_input_method = nil
  # 
  # to downcase instead:
  #
  #   Sequel.identifier_input_method = :downcase
  #
  # Other String instance methods work as well.
  def self.identifier_input_method=(value)
    Database.identifier_input_method = value
  end
  
  # Set the method to call on identifiers coming out of the database.  This affects
  # the literalization of identifiers by calling this method on them when they are
  # retrieved from the database.  Sequel downcases identifiers retrieved for most
  # databases, so to turn that off:
  #
  #   Sequel.identifier_output_method = nil
  # 
  # to upcase instead:
  #
  #   Sequel.identifier_output_method = :upcase
  #
  # Other String instance methods work as well.
  def self.identifier_output_method=(value)
    Database.identifier_output_method = value
  end
  
  # Set whether to quote identifiers for all databases by default. By default,
  # Sequel quotes identifiers in all SQL strings, so to turn that off:
  #
  #   Sequel.quote_identifiers = false
  def self.quote_identifiers=(value)
    Database.quote_identifiers = value
  end

  # Require all given files which should be in the same or a subdirectory of
  # this file.  If a subdir is given, assume all files are in that subdir.
  def self.require(files, subdir=nil)
    Array(files).each{|f| super("#{File.dirname(__FILE__)}/#{"#{subdir}/" if subdir}#{f}")}
  end
  
  # Set whether to set the single threaded mode for all databases by default. By default,
  # Sequel uses a threadsafe connection pool, which isn't as fast as the
  # single threaded connection pool.  If your program will only have one thread,
  # and speed is a priority, you may want to set this to true:
  #
  #   Sequel.single_threaded = true
  def self.single_threaded=(value)
    Database.single_threaded = value
  end

  # Converts the given string into a Date object.
  def self.string_to_date(s)
    begin
      Date.parse(s, Sequel.convert_two_digit_years)
    rescue => e
      raise convert_exception_class(e, InvalidValue)
    end
  end

  # Converts the given string into a Time or DateTime object, depending on the
  # value of Sequel.datetime_class.
  def self.string_to_datetime(s)
    begin
      if datetime_class == DateTime
        DateTime.parse(s, convert_two_digit_years)
      else
        datetime_class.parse(s)
      end
    rescue => e
      raise convert_exception_class(e, InvalidValue)
    end
  end

  # Converts the given string into a Time object.
  def self.string_to_time(s)
    begin
      Time.parse(s)
    rescue => e
      raise convert_exception_class(e, InvalidValue)
    end
  end

  # If the supplied block takes a single argument,
  # yield a new SQL::VirtualRow instance to the block
  # argument.  Otherwise, evaluate the block in the context of a new
  # SQL::VirtualRow instance.
  def self.virtual_row(&block)
    vr = SQL::VirtualRow.new
    case block.arity
    when -1, 0
      vr.instance_eval(&block)
    else
      block.call(vr)
    end  
  end
  
  ### Private Class Methods ###

  # Helper method that the database adapter class methods that are added to Sequel via
  # metaprogramming use to parse arguments.
  def self.adapter_method(adapter, *args, &block) # :nodoc:
    raise(::Sequel::Error, "Wrong number of arguments, 0-2 arguments valid") if args.length > 2
    opts = {:adapter=>adapter.to_sym}
    opts[:database] = args.shift if args.length >= 1 && !(args[0].is_a?(Hash))
    if Hash === (arg = args[0])
      opts.merge!(arg)
    elsif !arg.nil?
      raise ::Sequel::Error, "Wrong format of arguments, either use (), (String), (Hash), or (String, Hash)"
    end
    connect(opts, &block)
  end

  # Method that adds a database adapter class method to Sequel that calls
  # Sequel.adapter_method.
  def self.def_adapter_method(*adapters) # :nodoc:
    adapters.each do |adapter|
      instance_eval("def #{adapter}(*args, &block); adapter_method('#{adapter}', *args, &block) end", __FILE__, __LINE__)
    end
  end

  private_class_method :adapter_method, :def_adapter_method
  
  require(%w"metaprogramming sql connection_pool exceptions dataset database timezones version")
  require(%w"schema_generator schema_methods schema_sql", 'database')
  require(%w"actions convenience features graph prepared_statements query sql", 'dataset')
  require('core_sql') if !defined?(::SEQUEL_NO_CORE_EXTENSIONS) && !ENV.has_key?('SEQUEL_NO_CORE_EXTENSIONS')

  # Add the database adapter class methods to Sequel via metaprogramming
  def_adapter_method(*Database::ADAPTERS)
end
