module ActionAccess
  module ControllerAdditions
    module ClassMethods
      # Lock actions by default, they won't be accessible unless authorized.
      # It takes the same options as filter callbacks.
      def lock_access(options = {})
        before_action :validate_access!, options
      end

      # Is this controller locked?
      def access_locked?
        filters = _process_action_callbacks.collect(&:filter)
        :validate_access!.in? filters
      end

      # Set an access rule for the current controller.
      # It will automatically lock the controller if it wasn't already.
      #
      #
      # == Parameters
      #
      # +clearance_levels+:: single clearance level (string or symbol) or list
      #   of them (list of parameters or array), either singular or plural.
      #   Accepts the special keyword +:all+ (every clearance level, even none).
      #
      # +permissions+:: controller action (string or symbol) or list of them (array).
      #   Accepts the special keyword +:all+ (every action in the controller).
      #
      #
      # == Example:
      #
      #   class ArticlesControler < ApplicationController
      #     let :admins, :all                           # admins can do anything
      #     let :editors, :reviewers, [:edit, :update]  # editors and reviewers can edit articles
      #     let :all, [:index, :show]                   # anyone can view articles
      #
      #     # ...
      #   end
      #
      def let(*clearance_levels, permissions)
        lock_access unless access_locked?
        keeper = ActionAccess::Keeper.instance
        clearance_levels = Array(clearance_levels).flatten
        clearance_levels.each { |c| keeper.let c, permissions, self }
      end
    end


    def self.included(base)
      base.extend ClassMethods
      base.helper_method :keeper
    end


    private

      # Helper to access Keeper's instance.
      def keeper
        ActionAccess::Keeper.instance
      end

      # Current user's clearance levels (override to customize).
      def current_clearance_levels
        # Notify deprecation of `current_clearance_level` (singular)
        if defined? current_clearance_level
          ActiveSupport::Deprecation.warn \
            '[Action Access] The use of "current_clearance_level" '   +
            'is going to be deprecated in the next release, rename ' +
            'it to "current_clearance_levels" (plural).'
          return current_clearance_level
        end

        if defined?(current_user) and current_user.respond_to?(:clearance_levels)
          current_user.clearance_levels
        else
          :guest
        end
      end

      # Default path to redirect any non authorized access (override to customize).
      def unauthorized_access_redirection_path
        root_path
      end

      # Validate access to the current route.
      def validate_access!
        action = self.action_name
        clearance_levels = Array(current_clearance_levels)
        authorized = clearance_levels.any? { |c| keeper.lets? c, action, self.class }
        not_authorized! unless authorized
      end

      # Redirect if not authorized.
      # May be used inside action methods for finer control.
      def not_authorized!(*args)
        options = args.extract_options!
        message = options[:message] ||
          I18n.t('action_access.redirection_message', default: 'Not authorized.')
        path = options[:path] || unauthorized_access_redirection_path
        redirect_to path, alert: message
      end
  end
end
