module NCore
  module Associations

    def has_many(assoc, class_name: nil)
      assoc = assoc.to_s
      klass = class_name || "#{module_name}::#{assoc.camelize.singularize}"
      key = "#{attrib_name}_id"
      class_eval <<-M1, __FILE__, __LINE__+1
        def #{assoc}(params={})
          return [] unless id
          reload = params.delete :reload
          cacheable = params.except(:credentials, :request).empty?
          params = parse_request_params(params).reverse_merge credentials: api_creds
          params[:#{key}] = id
          if cacheable
            # only cache unfiltered, default api call
            @attribs[:#{assoc}] = (!reload && @attribs[:#{assoc}]) || #{klass}.all(params)
          else
            #{klass}.all(params)
          end
        end
      M1
      class_eval <<-M2, __FILE__, __LINE__+1
        def find_#{assoc.singularize}(aid, params={})
          raise UnsavedObjectError unless id
          params = parse_request_params(params).reverse_merge credentials: api_creds
          params[:#{key}] = id
          #{klass}.find(aid, params)
        end
      M2
      # will always return the object; check .errors? or .valid? to see how it went
      class_eval <<-M3, __FILE__, __LINE__+1
        def create_#{assoc.singularize}(params={})
          raise UnsavedObjectError unless id
          params = parse_request_params(params).reverse_merge credentials: api_creds
          params[:#{key}] = id
          #{klass}.create(params)
        end
      M3
      # will always return the object; check .errors? or .valid? to see how it went
      class_eval <<-M4, __FILE__, __LINE__+1
        def update_#{assoc.singularize}(aid, params={})
          raise UnsavedObjectError unless id
          params = parse_request_params(params).reverse_merge credentials: api_creds
          params[:#{key}] = id
          #{klass}.update(aid, params)
        end
      M4
      class_eval <<-M5, __FILE__, __LINE__+1
        def create_#{assoc.singularize}!(params={})
          raise UnsavedObjectError unless id
          params = parse_request_params(params).reverse_merge credentials: api_creds
          params[:#{key}] = id
          #{klass}.create!(params)
        end
      M5
      class_eval <<-M6, __FILE__, __LINE__+1
        def update_#{assoc.singularize}!(aid, params={})
          raise UnsavedObjectError unless id
          params = parse_request_params(params).reverse_merge credentials: api_creds
          params[:#{key}] = id
          #{klass}.update!(aid, params)
        end
      M6
      # will always return the object; check .errors? or .valid? to see how it went
      class_eval <<-M7, __FILE__, __LINE__+1
        def delete_#{assoc.singularize}(aid, params={})
          raise UnsavedObjectError unless id
          params = parse_request_params(params).reverse_merge credentials: api_creds
          params[:#{key}] = id
          #{klass}.delete(aid, params)
        end
      M7
      class_eval <<-M8, __FILE__, __LINE__+1
        def delete_#{assoc.singularize}!(aid, params={})
          raise UnsavedObjectError unless id
          params = parse_request_params(params).reverse_merge credentials: api_creds
          params[:#{key}] = id
          #{klass}.delete!(aid, params)
        end
      M8
    end

    def belongs_to(assoc, class_name: nil)
      assoc = assoc.to_s
      klass = class_name || "#{module_name}::#{assoc.camelize}"
      class_eval <<-M1, __FILE__, __LINE__+1
        attr :#{assoc}_id
        def #{assoc}(params={})
          return nil unless #{assoc}_id
          params = parse_request_params(params).reverse_merge credentials: api_creds
          if params.except(:credentials, :request).empty?
            # only cache unfiltered, default api call
            @attribs[:#{assoc}] ||= #{klass}.find(#{assoc}_id, params)
          else
            #{klass}.find(#{assoc}_id, params)
          end
        end
      M1
      class_eval <<-M2, __FILE__, __LINE__+1
        def #{assoc}_id=(v)
          @attribs[:#{assoc}] = nil unless @attribs[:#{assoc}_id] == v
          @attribs[:#{assoc}_id] = v
        end
        private :#{assoc}_id=
      M2
    end

  end
end
