require 'brakeman/tracker/collection'

module Brakeman
  module ControllerMethods
    attr_accessor :layout

    def initialize_controller
      @options[:before_filters] = []
      @options[:skip_filters] = []
      @layout = nil
      @skip_filter_cache = nil
      @before_filter_cache = nil
    end

    def protect_from_forgery?
      @options[:protect_from_forgery]
    end

    def add_before_filter exp
      @options[:before_filters] << exp
    end

    def prepend_before_filter exp
      @options[:before_filters].unshift exp
    end

    def before_filters
      @options[:before_filters]
    end

    def skip_filter exp
      @options[:skip_filters] << exp
    end

    def skip_filters
      @options[:skip_filters]
    end

    def before_filter_list processor, method
      controller = self
      filters = []

      while controller
        filters = controller.get_before_filters(processor, method) + filters

        controller = tracker.controllers[controller.parent] ||
          tracker.libs[controller.parent]
      end

      remove_skipped_filters processor, filters, method
    end

    def get_skipped_filters processor, method
      filters = []

      if @skip_filter_cache.nil?
        @skip_filter_cache = skip_filters.map do |filter|
          before_filter_to_hash(processor, filter.args)
        end
      end

      @skip_filter_cache.each do |f|
        if filter_includes_method? f, method
          filters.concat f[:methods]
        else
        end
      end

      filters
    end


    def remove_skipped_filters processor, filters, method
      controller = self

      while controller
        filters = filters - controller.get_skipped_filters(processor, method)

        controller = tracker.controllers[controller.parent] ||
          tracker.libs[controller.parent]
      end

      filters
    end

    def get_before_filters processor, method
      filters = []

      if @before_filter_cache.nil?
        @before_filter_cache = []

        before_filters.each do |filter|
          @before_filter_cache << before_filter_to_hash(processor, filter.args)
        end
      end

      @before_filter_cache.each do |f|
        if filter_includes_method? f, method
          filters.concat f[:methods]
        end
      end

      filters
    end

    def before_filter_to_hash processor, args
      filter = {}

      #Process args for the uncommon but possible situation
      #in which some variables are used in the filter.
      args.each do |a|
        if sexp? a
          a = processor.process_default a
        end
      end

      filter[:methods] = []

      args.each do |a|
        filter[:methods] << a[1] if a.node_type == :lit
      end

      options = args.last

      if hash? options
        # Probably only one option,
        # but this also avoids issues with kwsplats
        hash_iterate(options) do |option, value|
          case value.node_type
          when :array
            filter[option.value] = value.sexp_body.map {|v| v[1] }
          when :lit, :str
            filter[option.value] = value[1]
          else
            Brakeman.debug "Unknown before_filter value: #{option} => #{value}"
          end
        end
      else
        filter[:all] = true
      end

      filter
    end

    private

    def filter_includes_method? filter_rule, method_name
       filter_rule[:all] or
       (filter_rule[:only] == method_name) or
       (filter_rule[:only].is_a? Array and filter_rule[:only].include? method_name) or
       (filter_rule[:except].is_a? Symbol and filter_rule[:except] != method_name) or
       (filter_rule[:except].is_a? Array and not filter_rule[:except].include? method_name)
    end
  end

  class Controller < Brakeman::Collection
    include ControllerMethods

    def initialize name, parent, file_name, src, tracker
      super
      initialize_controller
      @collection = tracker.controllers
    end
  end
end
