# Copyright (c) 2011 Artem Kuzko
# Copyright (c) 2013 Zach Belzer
# Copyright (c) 2013 Arthur Shagall
# Released under the MIT license.  See LICENSE for details.

# Used to patch ActiveRecord reflections.
module PowerEnum::Reflection
  extend ActiveSupport::Concern

  # Class-level extensions injected into ActiveRecord
  module ClassMethods
    def self.extended(base) # :nodoc:
      class << base
        alias_method_chain :reflect_on_all_associations, :enumeration
        alias_method_chain :reflect_on_association, :enumeration
      end
    end

    # All {PowerEnum::Reflection::EnumerationReflection} reflections
    def reflect_on_all_enumerated
      # Need to give it a full namespace to avoid getting Rails confused in development
      # mode where all objects are reloaded on every request.
      reflections.values.grep(PowerEnum::Reflection::EnumerationReflection)
    end

    # If the reflection of the given name is an EnumerationReflection, returns
    # the reflection, otherwise returns nil.
    # @return [PowerEnum::Reflection::EnumerationReflection]
    def reflect_on_enumerated( enumerated )
      key = if Rails.version =~ /^4\.2.*/
              enumerated.to_s
            else
              enumerated.to_sym
            end
      reflections[key].is_a?(PowerEnum::Reflection::EnumerationReflection) ? reflections[key] : nil
    end

    # Extend associations with enumerations, preferring enumerations
    def reflect_on_all_associations_with_enumeration(macro = nil)
      reflect_on_all_enumerated + reflect_on_all_associations_without_enumeration(macro)
    end

    # Extend associations with enumerations, preferring enumerations
    def reflect_on_association_with_enumeration( associated )
      reflect_on_enumerated(associated) || reflect_on_association_without_enumeration(associated)
    end
  end

  # Reflection class for enum reflections.  See ActiveRecord::Reflection
  class EnumerationReflection < ActiveRecord::Reflection::MacroReflection
    attr_reader :counter_cache_column
    attr_accessor :parent_reflection

    # See ActiveRecord::Reflection::MacroReflection
    def initialize( name, options, active_record )
      if Rails.version =~ /^4\.2.*/
        super name, nil, options, active_record
      else
        super :has_enumerated, name, nil, options, active_record
      end
    end

    if Rails.version =~ /^4\.2.*/
      def macro
        :has_enumerated
      end

      def check_preloadable!
        return unless scope
        if scope.arity > 0
          ActiveSupport::Deprecation.warn(<<-MSG.squish)
The association scope '#{name}' is instance dependent (the scope
block takes an argument). Preloading happens before the individual
instances are created. This means that there is no instance being
passed to the association scope. This will most likely result in
broken or incorrect behavior. Joining, Preloading and eager loading
of these associations is deprecated and will be removed in the future.
          MSG
        end
      end
      alias :check_eager_loadable! :check_preloadable!

      def active_record_primary_key
        @active_record_primary_key ||= options[:primary_key] || active_record.primary_key
      end

      def klass
        @klass ||= active_record.send(:compute_type, class_name)
      end

      EnumJoinKeys = Struct.new(:key, :foreign_key)

      def join_keys(_)
        EnumJoinKeys.new(active_record_primary_key, foreign_key)
      end
    end

    # Returns the class name of the enum
    def class_name
      @class_name ||= (@options[:class_name] || @name).to_s.camelize
    end

    # Returns the foreign key on the association owner's table.
    def foreign_key
      @foreign_key ||= (@options[:foreign_key] || "#{@name}_id").to_s
    end

    # Returns the name of the enum table
    def table_name
      @table_name ||= self.class.const_get(class_name).table_name
    end

    # Returns the primary key of the active record model that owns the has_enumerated
    # association.
    def association_primary_key(klass = nil)
      active_record.primary_key
    end

    # Does nothing.
    def check_validity!; end

    # Returns nil
    def source_reflection;
      nil
    end

    # Returns nil
    def type
      nil
    end

    # Always returns false. Necessary for stuff like Booking.where(:status => BookingStatus[:confirmed])
    def polymorphic?
      false
    end

    # In this case, returns [[]]
    def conditions
      [[]]
    end

    # Returns :belongs_to.  Kind of hackish, but otherwise AREL joins logic
    # gets confused.
    def source_macro
      :belongs_to
    end

    # Returns an array of this instance as the only member.
    def chain
      [self]
    end

    # Normally defined on AR::AssociationReflection::MacroReflection.
    # Realistically, this is a belongs-to relationship.
    def belongs_to?
      true
    end

    # An array of arrays of scopes. Each item in the outside array corresponds
    # to a reflection in the #chain.
    def scope_chain
      scope ? [[scope]] : [[]]
    end

  end
end
