require 'addressable/uri'
require 'bigdecimal'
require 'bigdecimal/util'
require 'date'
require 'pathname'
require 'set'
require 'time'
require 'yaml'

module DataMapper
  module Undefined; end
end

require 'dm-core/support/ext/blank'
require 'dm-core/support/ext/hash'
require 'dm-core/support/ext/object'
require 'dm-core/support/ext/string'

begin
  require 'fastthread'
rescue LoadError
  # fastthread not installed
end

require 'dm-core/core_ext/pathname'
require 'dm-core/support/ext/module'
require 'dm-core/support/ext/array'
require 'dm-core/support/ext/try_dup'

require 'dm-core/support/mash'
require 'dm-core/support/inflector/inflections'
require 'dm-core/support/inflector/methods'
require 'dm-core/support/inflections'
require 'dm-core/support/chainable'
require 'dm-core/support/deprecate'
require 'dm-core/support/descendant_set'
require 'dm-core/support/equalizer'
require 'dm-core/support/assertions'
require 'dm-core/support/lazy_array'
require 'dm-core/support/local_object_space'
require 'dm-core/support/hook'
require 'dm-core/support/subject'
require 'dm-core/support/ordered_set'
require 'dm-core/support/subject_set'

require 'dm-core/query'
require 'dm-core/query/conditions/operation'
require 'dm-core/query/conditions/comparison'
require 'dm-core/query/operator'
require 'dm-core/query/direction'
require 'dm-core/query/path'
require 'dm-core/query/sort'

require 'dm-core/resource'
require 'dm-core/resource/persistence_state'
require 'dm-core/resource/persistence_state/transient'
require 'dm-core/resource/persistence_state/immutable'
require 'dm-core/resource/persistence_state/persisted'
require 'dm-core/resource/persistence_state/clean'
require 'dm-core/resource/persistence_state/deleted'
require 'dm-core/resource/persistence_state/dirty'

require 'dm-core/property/invalid_value_error'
require 'dm-core/property'
require 'dm-core/property/typecast/numeric'
require 'dm-core/property/typecast/time'
require 'dm-core/property/object'
require 'dm-core/property/string'
require 'dm-core/property/binary'
require 'dm-core/property/text'
require 'dm-core/property/numeric'
require 'dm-core/property/float'
require 'dm-core/property/decimal'
require 'dm-core/property/boolean'
require 'dm-core/property/integer'
require 'dm-core/property/serial'
require 'dm-core/property/date'
require 'dm-core/property/date_time'
require 'dm-core/property/time'
require 'dm-core/property/class'
require 'dm-core/property/discriminator'
require 'dm-core/property/lookup'
require 'dm-core/property_set'

require 'dm-core/model'
require 'dm-core/model/hook'
require 'dm-core/model/is'
require 'dm-core/model/scope'
require 'dm-core/model/relationship'
require 'dm-core/model/property'

require 'dm-core/collection'
require 'dm-core/relationship_set'
require 'dm-core/associations/relationship'
require 'dm-core/associations/one_to_many'
require 'dm-core/associations/one_to_one'
require 'dm-core/associations/many_to_one'
require 'dm-core/associations/many_to_many'

require 'dm-core/identity_map'
require 'dm-core/repository'
require 'dm-core/adapters'
require 'dm-core/adapters/abstract_adapter'

require 'dm-core/support/logger'
require 'dm-core/support/naming_conventions'
require 'dm-core/version'

require 'dm-core/core_ext/kernel'             # TODO: do not load automatically
require 'dm-core/core_ext/symbol'             # TODO: do not load automatically

require 'dm-core/backwards'                   # TODO: do not load automatically

# A logger should always be present. Lets be consistent with DO
DataMapper::Logger.new(StringIO.new, :fatal)

unless defined?(Infinity)
  Infinity = 1.0/0
end

# == Setup and Configuration
# DataMapper uses URIs or a connection hash to connect to your data-store.
# URI connections takes the form of:
#   DataMapper.setup(:default, 'protocol://username:password@127.0.0.1:port/path/to/repo')
#
# Breaking this down, the first argument is the name you wish to give this
# connection.  If you do not specify one, it will be assigned :default. If you
# would like to connect to more than one data-store, simply issue this command
# again, but with a different name specified.
#
# In order to issue ORM commands without specifying the repository context, you
# must define the :default database. Otherwise, you'll need to wrap your ORM
# calls in <tt>repository(:name) { }</tt>.
#
# Second, the URI breaks down into the access protocol, the username, the
# server, the password, and whatever path information is needed to properly
# address the data-store on the server.
#
# Here's some examples
#   DataMapper.setup(:default, 'sqlite3://path/to/your/project/db/development.db')
#   DataMapper.setup(:default, 'mysql://127.0.0.1/dm_core_test')
#     # no auth-info
#   DataMapper.setup(:default, 'postgres://root:supahsekret@127.0.0.1/dm_core_test')
#     # with auth-info
#
#
# Alternatively, you can supply a hash as the second parameter, which would
# take the form:
#
#   DataMapper.setup(:default, {
#     :adapter  => 'adapter_name_here',
#     :database => 'path/to/repo',
#     :username => 'username',
#     :password => 'password',
#     :host     => 'hostname'
#   })
#
# === Logging
# To turn on error logging to STDOUT, issue:
#
#   DataMapper::Logger.new($stdout, :debug)
#
# You can pass a file location ("/path/to/log/file.log") in place of $stdout.
# see DataMapper::Logger for more information.
#
module DataMapper
  extend DataMapper::Assertions

  class RepositoryNotSetupError < StandardError; end

  class IncompleteModelError < StandardError; end

  class PluginNotFoundError < StandardError; end

  class UnknownRelationshipError < StandardError; end

  class ObjectNotFoundError < RuntimeError; end

  class PersistenceError < RuntimeError; end

  class UpdateConflictError < PersistenceError; end

  class SaveFailureError < PersistenceError
    attr_reader :resource

    def initialize(message, resource)
      super(message)
      @resource = resource
    end
  end

  class ImmutableError < RuntimeError; end

  class ImmutableDeletedError < ImmutableError; end

  # Raised on attempt to operate on collection of child objects
  # when parent object is not yet saved.
  # For instance, if your article object is not saved,
  # but you try to fetch or scope down comments (1:n case), or
  # publications (n:m case), operation cannot be completed
  # because parent object's keys are not yet persisted,
  # and thus there is no FK value to use in the query.
  class UnsavedParentError < PersistenceError; end

  # @api private
  def self.root
    @root ||= Pathname(__FILE__).dirname.parent.expand_path.freeze
  end

  # Setups up a connection to a data-store
  #
  # @param [Symbol] name
  #   a name for the context, defaults to :default
  # @param [Hash(Symbol => String), Addressable::URI, String] uri_or_options
  #   connection information
  #
  # @return [DataMapper::Adapters::AbstractAdapter]
  #   the resulting setup adapter
  #
  # @raise [ArgumentError] "+name+ must be a Symbol, but was..."
  #   indicates that an invalid argument was passed for name[Symbol]
  # @raise [ArgumentError] "+uri_or_options+ must be a Hash, URI or String, but was..."
  #   indicates that connection information could not be gleaned from
  #   the given uri_or_options[Hash, Addressable::URI, String]
  #
  # @api public
  def self.setup(*args)
    adapter = args.first

    unless adapter.kind_of?(Adapters::AbstractAdapter)
      adapter = Adapters.new(*args)
    end

    Repository.adapters[adapter.name] = adapter
  end

  # Block Syntax
  #   Pushes the named repository onto the context-stack,
  #   yields a new session, and pops the context-stack.
  #
  # Non-Block Syntax
  #   Returns the current session, or if there is none,
  #   a new Session.
  #
  # @param [Symbol] args the name of a repository to act within or return, :default is default
  #
  # @yield [Proc] (optional) block to execute within the context of the named repository
  #
  # @api public
  def self.repository(name = nil)
    context = Repository.context

    current_repository = if name
      name = name.to_sym
      context.detect { |repository| repository.name == name }
    else
      name = Repository.default_name
      context.last
    end

    current_repository ||= Repository.new(name)

    if block_given?
      current_repository.scope { |*block_args| yield(*block_args) }
    else
      current_repository
    end
  end

  # Perform necessary steps to finalize DataMapper for the current repository
  #
  # This method should be called after loading all models and plugins.
  #
  # It ensures foreign key properties and anonymous join models are created.
  # These are otherwise lazily declared, which can lead to unexpected errors.
  # It also performs basic validity checking of the DataMapper models.
  #
  # @return [self]
  #
  # @api public
  def self.finalize
    Model.descendants.each { |model| model.finalize }
    self
  end

end # module DataMapper
