# encoding: utf-8
require 'dynamoid/associations/association'
require 'dynamoid/associations/single_association'
require 'dynamoid/associations/many_association'
require 'dynamoid/associations/has_many'
require 'dynamoid/associations/belongs_to'
require 'dynamoid/associations/has_one'
require 'dynamoid/associations/has_and_belongs_to_many'

module Dynamoid

  # Connects models together through the magic of associations. We enjoy four different kinds of associations presently:
  #   * belongs_to
  #   * has_and_belongs_to_many
  #   * has_many
  #   * has_one
  module Associations
    extend ActiveSupport::Concern
    
    # Create the association tracking attribute and initialize it to an empty hash.
    included do
      class_attribute :associations
      
      self.associations = {}
    end

    module ClassMethods
      
      # create a has_many association for this document.
      #
      # @param [Symbol] name the name of the association
      # @param [Hash] options options to pass to the association constructor
      # @option options [Class] :class the target class of the has_many association; that is, the belongs_to class
      # @option options [Symbol] :class_name the name of the target class of the association; that is, the name of the belongs_to class
      # @option options [Symbol] :inverse_of the name of the association on the target class; that is, if the class has a belongs_to association, the name of that association
      #
      # @since 0.2.0
      def has_many(name, options = {})
        association(:has_many, name, options)
      end
      
      # create a has_one association for this document.
      #
      # @param [Symbol] name the name of the association
      # @param [Hash] options options to pass to the association constructor
      # @option options [Class] :class the target class of the has_one association; that is, the belongs_to class
      # @option options [Symbol] :class_name the name of the target class of the association; that is, the name of the belongs_to class
      # @option options [Symbol] :inverse_of the name of the association on the target class; that is, if the class has a belongs_to association, the name of that association
      #
      # @since 0.2.0
      def has_one(name, options = {})
        association(:has_one, name, options)
      end
      
      # create a belongs_to association for this document.
      #
      # @param [Symbol] name the name of the association
      # @param [Hash] options options to pass to the association constructor
      # @option options [Class] :class the target class of the has_one association; that is, the has_many or has_one class
      # @option options [Symbol] :class_name the name of the target class of the association; that is, the name of the has_many or has_one class
      # @option options [Symbol] :inverse_of the name of the association on the target class; that is, if the class has a has_many or has_one association, the name of that association
      #
      # @since 0.2.0
      def belongs_to(name, options = {})
        association(:belongs_to, name, options)
      end
      
      # create a has_and_belongs_to_many association for this document.
      #
      # @param [Symbol] name the name of the association
      # @param [Hash] options options to pass to the association constructor
      # @option options [Class] :class the target class of the has_and_belongs_to_many association; that is, the belongs_to class
      # @option options [Symbol] :class_name the name of the target class of the association; that is, the name of the belongs_to class
      # @option options [Symbol] :inverse_of the name of the association on the target class; that is, if the class has a belongs_to association, the name of that association
      #
      # @since 0.2.0
      def has_and_belongs_to_many(name, options = {})
        association(:has_and_belongs_to_many, name, options)
      end
      
      private

      # create getters and setters for an association.
      #
      # @param [Symbol] symbol the type (:has_one, :has_many, :has_and_belongs_to_many, :belongs_to) of the association
      # @param [Symbol] name the name of the association
      # @param [Hash] options options to pass to the association constructor; see above for all valid options
      #
      # @since 0.2.0      
      def association(type, name, options = {})
        field "#{name}_ids".to_sym, :set
        self.associations[name] = options.merge(:type => type)
        define_method(name) do
          @associations[:"#{name}_ids"] ||= Dynamoid::Associations.const_get(type.to_s.camelcase).new(self, name, options)
        end
        define_method("#{name}=".to_sym) do |objects|
          @associations[:"#{name}_ids"] ||= Dynamoid::Associations.const_get(type.to_s.camelcase).new(self, name, options)
          @associations[:"#{name}_ids"].setter(objects)
        end
      end
    end


  end
  
end