# encoding: utf-8
module Mongoid #:nodoc:
  module Associations #:nodoc:
    class HasOne #:nodoc:
      include Proxy

      attr_reader :association_name, :document, :parent, :options

      # Build a new object for the association.
      def build(attrs = {}, type = nil)
        @document = attrs.assimilate(@parent, @options, type)
        self
      end

      # Create a new object for the association and save it.
      def create(attrs = {}, type = nil)
        build(attrs, type)
        @document.save
        self
      end

      # Creates the new association by finding the attributes in
      # the parent document with its name, and instantiating a
      # new document for it.
      #
      # All method calls on this object will then be delegated
      # to the internal document itself.
      #
      # Options:
      #
      # document: The parent +Document+
      # attributes: The attributes of the decorated object.
      # options: The association options.
      def initialize(document, attributes, options)
        @parent, @options, @association_name = document, options, options.name
        klass = attributes[:_type] ? attributes[:_type].constantize : nil
        @document = attributes.assimilate(@parent, @options, klass)
      end

      # Delegate all missing methods over to the +Document+.
      def method_missing(name, *args, &block)
        @document.send(name, *args, &block)
      end

      # Used for setting the association via a nested attributes setter on the
      # parent +Document+.
      def nested_build(attributes)
        build(attributes)
      end

      # This will get deprecated
      def to_a
        [@document]
      end

      # Need to override here for when the underlying document is nil.
      def valid?
        @document.valid?
      end

      class << self
        # Preferred method of instantiating a new +HasOne+, since nil values
        # will be handled properly.
        #
        # Options:
        #
        # document: The parent +Document+
        # options: The association options.
        def instantiate(document, options)
          attributes = document.attributes[options.name]
          return nil if attributes.blank?
          new(document, attributes, options)
        end

        # Returns the macro used to create the association.
        def macro
          :has_one
        end

        # Perform an update of the relationship of the parent and child. This
        # will assimilate the child +Document+ into the parent's object graph.
        #
        # Options:
        #
        # child: The child +Document+ or +Hash+.
        # parent: The parent +Document+ to update.
        # options: The association +Options+
        #
        # Example:
        #
        # <tt>HasOne.update({:first_name => "Hank"}, person, options)</tt>
        def update(child, parent, options)
          child.assimilate(parent, options)
        end
      end

    end
  end
end