module ReactiveResource
  module Association
    # Represents and resolves a belongs_to association.
    class BelongsToAssociation

      # The class this association is attached to
      attr_reader :klass

      # The attribute name this association represents
      attr_reader :attribute

      # additional options passed in when the association was created
      attr_reader :options

      # Returns the class name of the target of the association. Based
      # off of +attribute+ unless +class_name+ was passed in the
      # +options+ hash.
      def associated_class
        if options[:class_name]
          options[:class_name].constantize
        else
          klass.relative_const_get(attribute.to_s.camelize)
        end
      end

      # A flattened list of attributes from the entire association
      # +belongs_to+ hierarchy, including this association's attribute.
      def associated_attributes
        attributes = [attribute]
        if associated_class
          attributes += associated_class.belongs_to_associations.map(&:attribute)
        end
        attributes.uniq
      end

      # Called when this assocation is referenced. Finds and returns
      # the target of this association.
      def resolve_relationship(object)
        parent_params = object.prefix_options.dup
        parent_params.delete("#{attribute}_id".intern)
        associated_class.find(object.send("#{attribute}_id"), :params => parent_params)
      end
      
      # Adds methods for belongs_to associations, to make dealing with
      # these objects a bit more straightforward. If the attribute name
      # is +lawyer+, it will add:
      #
      # [lawyer] returns the actual lawyer object (after doing a web request)
      # [lawyer_id] returns the lawyer id
      # [lawyer_id=] sets the lawyer id
      def add_helper_methods(klass, attribute)
        association = self
        
        klass.class_eval do 
          # address.lawyer_id
          define_method("#{attribute}_id") do
            prefix_options["#{attribute}_id".intern]
            super()
          end
          
          # address.lawyer_id = 3
          define_method("#{attribute}_id=") do |value|
            prefix_options["#{attribute}_id".intern] = value
            super(value)
          end

          # address.lawyer
          define_method(attribute) do
            # if the parent has its own belongs_to associations, we need
            # to add those to the 'find' call. So, let's grab all of
            # these associations, turn them into a hash of :attr_name =>
            # attr_id, and fire off the find.
            
            unless instance_variable_get("@#{attribute}")
              object = association.resolve_relationship(self)
              instance_variable_set("@#{attribute}", object)
            end
            instance_variable_get("@#{attribute}")
          end
        end
        
        # Recurse through the parent object.
        associated_class.belongs_to_associations.each do |parent_attribute|
          parent_attribute.add_helper_methods(klass, parent_attribute.attribute)
        end
      end

      # Create a new belongs_to association.
      def initialize(klass, attribute, options)
        @klass = klass
        @attribute = attribute
        @options = options

        add_helper_methods(klass, attribute)
      end
    end
  end
end
