# encoding: utf-8

module Policy

  # Policy object class interface
  #
  # Includes <tt>ActiveModel::Validation</tt> and a list of methods
  # to compose policies.
  module Base

    # Composes the policy with others by AND method
    #
    # @overload and(*others)
    #   Returns a composition of the policy with other policies
    #
    #   The composition is valid when all policies are valid.
    #
    #   @example
    #     composition = one_policy.and(another)
    #     one_policy.valid?  # => true
    #     another.valid?     # => false
    #     composition.valid? # => false
    #
    #   @param [Policy::Base, Array<Policy::Base>] others
    #     other policies to compose the current policy with
    #
    #   @return [Policy::Base] the composed policy
    #
    # @overload and()
    #   Returns a negator object expecting negation of another policy
    #
    #   @example
    #     composition = one_policy.and.not(another)
    #     one_policy.valid?  # => true
    #     another.valid?     # => false
    #     composition.valid? # => true
    #
    #   @return [#not]
    def and(*others)
      compose And, others
    end

    # Composes the policy with others by OR method
    #
    # @overload or(*others)
    #   Returns a composition of the policy with other policies
    #
    #   The composition is valid when any policy is valid.
    #
    #   @example
    #     composition = one_policy.or(another)
    #     one_policy.valid?  # => true
    #     another.valid?     # => false
    #     composition.valid? # => true
    #
    #   @param [Policy::Base, Array<Policy::Base>] others
    #     other policies to compose the current policy with
    #
    #   @return [Policy::Base] the composed policy
    #
    # @overload or()
    #   Returns a negator object expecting negation of another policy
    #
    #   @example
    #     composition = one_policy.or.not(another)
    #     one_policy.valid?  # => false
    #     another.valid?     # => false
    #     composition.valid? # => true
    #
    #   @return [#not]
    def or(*others)
      compose Or, others
    end

    # Composes the policy with others by XOR method
    #
    # @overload xor(another)
    #   Returns a composition of the policy with other policies
    #
    #   The composition is valid when both valid and invalid policies
    #   are present
    #
    #   @example
    #     composition = one_policy.xor(another)
    #     one_policy.valid?  # => true
    #     another.valid?     # => true
    #     composition.valid? # => false
    #
    #   @param [Policy::Base, Array<Policy::Base>] others
    #     other policies to compose the current policy with
    #
    #   @return [Policy::Base] the composed policy
    #
    # @overload xor()
    #   Returns a negator object expecting negation of another policy
    #
    #   @example
    #     composition = one_policy.xor.not(another)
    #     one_policy.valid?  # => false
    #     another.valid?     # => false
    #     composition.valid? # => true
    #
    #   @return [#not]
    def xor(*others)
      compose Xor, others
    end

    # @private
    def self.included(klass)
      klass.instance_eval { include ActiveModel::Validations }
    end

    private

    def compose(composer, policies)
      return composer.new(self, *policies) if policies.any?
      Negator.new(self, composer)
    end

  end # module Base

end # module Policy