module JSONAPI
  class Relationship
    attr_reader :acts_as_set, :foreign_key, :options, :name,
                :class_name, :polymorphic, :always_include_linkage_data,
                :parent_resource, :eager_load_on_include

    attr_accessor :_routed, :_warned_missing_route

    def initialize(name, options = {})
      @name = name.to_s
      @options = options
      @acts_as_set = options.fetch(:acts_as_set, false) == true
      @foreign_key = options[:foreign_key] ? options[:foreign_key].to_sym : nil
      @parent_resource = options[:parent_resource]
      @relation_name = options.fetch(:relation_name, @name)
      @polymorphic = options.fetch(:polymorphic, false) == true
      @always_include_linkage_data = options.fetch(:always_include_linkage_data, false) == true
      @eager_load_on_include = options.fetch(:eager_load_on_include, true) == true
      @_routed = false
      @_warned_missing_route = false

      exclude_links(options.fetch(:exclude_links, JSONAPI.configuration.default_exclude_links))
    end

    alias_method :polymorphic?, :polymorphic

    def primary_key
      @primary_key ||= resource_klass._primary_key
    end

    def resource_klass
      @resource_klass ||= @parent_resource.resource_for(@class_name)
    end

    def table_name
      @table_name ||= resource_klass._table_name
    end

    def type
      @type ||= resource_klass._type.to_sym
    end

    def relation_name(options)
      case @relation_name
        when Symbol
          # :nocov:
          @relation_name
          # :nocov:
        when String
          @relation_name.to_sym
        when Proc
          @relation_name.call(options)
      end
    end

    def type_for_source(source)
      if polymorphic?
        resource = source.public_send(name)
        resource.class._type if resource
      else
        type
      end
    end

    def belongs_to?
      false
    end

    def exclude_links(exclude)
      case exclude
        when :default, "default"
          @_exclude_links = [:self, :related]
        when :none, "none"
          @_exclude_links = []
        when Array
          @_exclude_links = exclude.collect {|link| link.to_sym}
        else
          fail "Invalid exclude_links"
      end
    end

    def _exclude_links
      @_exclude_links ||= []
    end

    def exclude_link?(link)
      _exclude_links.include?(link.to_sym)
    end

    class ToOne < Relationship
      attr_reader :foreign_key_on

      def initialize(name, options = {})
        super
        @class_name = options.fetch(:class_name, name.to_s.camelize)
        @foreign_key ||= "#{name}_id".to_sym
        @foreign_key_on = options.fetch(:foreign_key_on, :self)
      end

      def to_s
        # :nocov:
        "#{parent_resource}.#{name}(#{belongs_to? ? 'BelongsToOne' : 'ToOne'})"
        # :nocov:
      end

      def belongs_to?
        foreign_key_on == :self
      end

      def polymorphic_type
        "#{name}_type" if polymorphic?
      end
    end

    class ToMany < Relationship
      attr_reader :reflect, :inverse_relationship

      def initialize(name, options = {})
        super
        @class_name = options.fetch(:class_name, name.to_s.camelize.singularize)
        @foreign_key ||= "#{name.to_s.singularize}_ids".to_sym
        @reflect = options.fetch(:reflect, true) == true
        @inverse_relationship = options.fetch(:inverse_relationship, parent_resource._type.to_s.singularize.to_sym) if parent_resource
      end

      def to_s
        # :nocov:
        "#{parent_resource}.#{name}(ToMany)"
        # :nocov:
      end
    end
  end
end