# encoding: utf-8

module Attestor

  module Validations

    # Describe a validator for class instances
    #
    # @example
    #   validator = Validator.new(:foo, policy: :bar, only: :baz)
    #
    #   validator.used_in_context? :baz # => true
    #   validator.validate object
    #
    # @api private
    class Validator

      # @!scope class
      # @!method new(name, except: [], only: [])
      # Creates a named item with blacklist or whitelist of contexts
      #
      # @param  [#to_sym] name
      # @option [#to_sym, Array<#to_sym>] :except
      # @option [#to_sym, Array<#to_sym>] :only
      #
      # @return [Attestor::Validations::Validator]

      # @private
      def initialize(name, except: nil, only: nil, policy: nil)
        @name      = name.to_sym
        @policy    = policy
        @whitelist = normalize(only)
        @blacklist = normalize(except)
        generate_id
        freeze
      end

      # @!attribute [r] name
      # The name of the item
      #
      # @return [Symbol]
      attr_reader :name

      # @!method policy?
      # Whether the validator uses a policy
      #
      # @return [Boolean]
      def policy?
        @policy ? true : false
      end

      # Compares an item to another one
      #
      # @param [Object] other
      #
      # @return [Boolean]
      def ==(other)
        other.instance_of?(self.class) ? id.equal?(other.id) : false
      end

      # Checks if the item should be used in given context
      #
      # @param [#to_sym] context
      #
      # @return [Boolean]
      def used_in_context?(context)
        symbol = context.to_sym
        whitelisted?(symbol) && !blacklisted?(symbol)
      end

      # Validates given object
      #
      # @param [Object] object
      #
      # @raise [Attestor::InvalidError]
      #   if object doesn't match validation rule
      #
      # @return [undefined]
      def validate(object)
        result = object.__send__(name)
        object.__send__(:invalid, name) if policy? && result.invalid?
      end

      protected

      # @!attribute [r] id
      # The item's identity
      #
      # @return [String]
      attr_reader :id

      private

      attr_reader :whitelist, :blacklist

      def whitelisted?(symbol)
        whitelist.empty? || whitelist.include?(symbol)
      end

      def blacklisted?(symbol)
        blacklist.include? symbol
      end

      def generate_id
        @id = [name, whitelist, blacklist].hash
      end

      def normalize(list)
        Array(list).map(&:to_sym).uniq
      end

    end # class Validator

  end # module Validations

end # module Attestor