module Devise
  class BaseSanitizer
    attr_reader :params, :resource_name, :resource_class

    def initialize(resource_class, resource_name, params)
      @resource_class = resource_class
      @resource_name  = resource_name
      @params         = params
      @blocks         = Hash.new
    end

    def for(kind, &block)
      if block_given?
        @blocks[kind] = block
      else
        default_for(kind)
      end
    end

    def sanitize(kind)
      if block = @blocks[kind]
        block.call(default_params)
      else
        default_sanitize(kind)
      end
    end

    private

    def default_for(kind)
      raise ArgumentError, "a block is expected in Devise base sanitizer"
    end

    def default_sanitize(kind)
      default_params
    end

    def default_params
      params.fetch(resource_name, {})
    end
  end

  class ParameterSanitizer < BaseSanitizer
    def initialize(*)
      super
      @permitted = Hash.new { |h,k| h[k] = attributes_for(k) }
    end

    def sign_in
      permit self.for(:sign_in)
    end

    def sign_up
      permit self.for(:sign_up)
    end

    def account_update
      permit self.for(:account_update)
    end

    private

    # TODO: We do need to flatten so it works with strong_parameters
    # gem. We should drop it once we move to Rails 4 only support.
    def permit(keys)
      default_params.permit(*Array(keys))
    end

    # Change for(kind) to return the values in the @permitted
    # hash, allowing the developer to customize at runtime.
    def default_for(kind)
      @permitted[kind] || raise("No sanitizer provided for #{kind}")
    end

    def default_sanitize(kind)
      if respond_to?(kind, true)
        send(kind)
      else
        raise NotImplementedError, "Devise doesn't know how to sanitize parameters for #{kind}"
      end
    end

    def attributes_for(kind)
      case kind
      when :sign_in
        auth_keys + [:password, :remember_me]
      when :sign_up
        auth_keys + [:password, :password_confirmation]
      when :account_update
        auth_keys + [:password, :password_confirmation, :current_password]
      end
    end

    def auth_keys
      @auth_keys ||= @resource_class.authentication_keys.respond_to?(:keys) ?
                       @resource_class.authentication_keys.keys : @resource_class.authentication_keys
    end
  end
end
