module Eaco
  module DSL

    ##
    # Parses the Resource definition DSL.
    #
    # Example:
    #
    #   authorize Document do
    #     roles :owner, :editor, :reader
    #
    #     role :owner, 'Author'
    #
    #     permissions do
    #       reader   :read
    #       editor   reader, :edit
    #       owner    editor, :destroy
    #     end
    #   end
    #
    # The DSL installs authorization in your +Document+ model,
    # defining three access roles.
    #
    # The +owner+ role is given a label of "Author".
    #
    # Each role has then different abilities, defined in the
    # permissions block.
    #
    # @see DSL::Resource::Permissions
    #
    class Resource < Base
      autoload :Permissions, 'eaco/dsl/resource/permissions'

      ##
      # Sets up an authorized resource. The only required API
      # is +accessible_by+. For available implementations, see
      # the {Adapters} module.
      #
      # @see Resource
      #
      def initialize(*)
        super

        target_eval do
          include Eaco::Resource

          def permissions
            @_permissions
          end

          def roles
            @_roles || []
          end

          def roles_with_labels
            @_roles_with_labels ||= roles.inject({}) do |labels, role|
              labels.update(role => role.to_s.humanize)
            end
          end

          # Reset memoizations when this method is called on the target class,
          # so that reloading the authorizations configuration file will
          # refresh the models' configuration.
          @_roles_with_labels = nil
        end
      end

      ##
      # Defines the permissions on this resource.
      # The evaluated registries are memoized in the target class.
      #
      # @return [void]
      #
      def permissions(&block)
        target_eval do
          @_permissions = Permissions.eval(self, &block).result.freeze
        end
      end

      ##
      # Defines the roles valid for this resource. e.g.
      #
      #     authorize Foobar do
      #       roles :owner, :editor, :reader
      #     end
      #
      # If the same user is at the same time +reader+ and +editor+, the
      # resulting role is +editor+.
      #
      # @param keys [Variadic]
      #
      # @return [void]
      #
      def roles(*keys)
        target_eval do
          @_roles = keys.flatten.freeze
        end
      end

      ##
      # Sets the given label on the given role.
      #
      # TODO rename this method, or use it to pass options
      # to improve readability of the DSL and to store more
      # metadata with each role for future extensibility.
      #
      # @param role [Symbol]
      # @param label [String]
      #
      # @return [void]
      #
      def role(role, label)
        target_eval do
          roles_with_labels[role] = label
        end
      end
    end

  end
end
