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

      # Appends the object to the +Array+, setting its parent in
      # the process.
      def <<(*objects)
        objects.flatten.each do |object|
          object.send("#{@foreign_key}=", @parent.id)
          @target << object
          object.save unless @parent.new_record?
        end
      end

      # Builds a new Document and adds it to the association collection. The
      # document created will be of the same class as the others in the
      # association, and the attributes will be passed into the constructor.
      #
      # Returns the newly created object.
      def build(attributes = {})
        name = @parent.class.to_s.underscore
        object = @klass.instantiate(attributes.merge(name => @parent))
        @target << object
        object
      end

      # Delegates to <<
      def concat(*objects)
        self << objects
      end

      # Creates a new Document and adds it to the association collection. The
      # document created will be of the same class as the others in the
      # association, and the attributes will be passed into the constructor and
      # the new object will then be saved.
      #
      # Returns the newly created object.
      def create(attributes)
        object = build(attributes)
        object.save
        object
      end

      # Finds a document in this association.
      # If an id is passed, will return the document for that id.
      def find(*args)
        args[1][:conditions].merge!(@foreign_key.to_sym => @parent.id) if args.size > 1
        @klass.find(*args)
      end

      # Initializing a related association only requires looking up the objects
      # by their ids.
      #
      # Options:
      #
      # document: The +Document+ that contains the relationship.
      # options: The association +Options+.
      def initialize(document, options, target = nil)
        @parent, @klass = document, options.klass
        @foreign_key = options.foreign_key
        @target = target || @klass.all(:conditions => { @foreign_key => document.id })
        extends(options)
      end

      # Delegates to <<
      def push(*objects)
        self << objects
      end

      class << self
        # Preferred method for creating the new +HasManyRelated+ association.
        #
        # Options:
        #
        # document: The +Document+ that contains the relationship.
        # options: The association +Options+.
        def instantiate(document, options, target = nil)
          new(document, options, target)
        end

        # Returns the macro used to create the association.
        def macro
          :has_many_related
        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:
        #
        # related: The related object
        # parent: The parent +Document+ to update.
        # options: The association +Options+
        #
        # Example:
        #
        # <tt>RelatesToOne.update(game, person, options)</tt>
        def update(target, document, options)
          name = document.class.to_s.underscore
          target.each { |child| child.send("#{name}=", document) }
          instantiate(document, options, target)
        end
      end

    end
  end
end