require 'set'
require 'brakeman/processors/alias_processor'
require 'brakeman/processors/lib/render_helper'
require 'brakeman/tracker'

#Processes aliasing in templates.
#Handles calls to +render+.
class Brakeman::TemplateAliasProcessor < Brakeman::AliasProcessor
  include Brakeman::RenderHelper

  FORM_METHODS = Set[:form_for, :remote_form_for, :form_remote_for]

  def initialize tracker, template, called_from = nil
    super tracker
    @template = template
    @called_from = called_from
  end

  #Process template
  def process_template name, args
    if @called_from
      unless @called_from.grep(/Template:#{name}$/).empty?
        Brakeman.debug "Skipping circular render from #{@template[:name]} to #{name}"
        return
      end

      super name, args, @called_from + ["Template:#{@template[:name]}"]
    else
      super name, args, ["Template:#{@template[:name]}"]
    end
  end

  #Determine template name
  def template_name name
    unless name.to_s.include? "/"
      name = "#{@template[:name].to_s.match(/^(.*\/).*$/)[1]}#{name}"
    end
    name
  end

  UNKNOWN_MODEL_CALL = Sexp.new(:call, Sexp.new(:const, Brakeman::Tracker::UNKNOWN_MODEL), :new)
  FORM_BUILDER_CALL = Sexp.new(:call, Sexp.new(:const, :FormBuilder), :new)

  #Looks for form methods and iterating over collections of Models
  def process_call_with_block exp
    process_default exp

    call = exp.block_call

    if call? call
      target = call.target
      method = call.method
      arg = exp.block_args.first_param
      block = exp.block

      #Check for e.g. Model.find.each do ... end
      if method == :each and arg and block and model = get_model_target(target)
        if arg.is_a? Symbol
          if model == target.target
            env[Sexp.new(:lvar, arg)] = Sexp.new(:call, model, :new)
          else
            env[Sexp.new(:lvar, arg)] = UNKNOWN_MODEL_CALL
          end

          process block if sexp? block
        end
      elsif FORM_METHODS.include? method
        if arg.is_a? Symbol
          env[Sexp.new(:lvar, arg)] = FORM_BUILDER_CALL

          process block if sexp? block
        end
      end
    end

    exp
  end

  alias process_iter process_call_with_block

  #Checks if +exp+ is a call to Model.all or Model.find*
  def get_model_target exp
    if call? exp
      target = exp.target

      if exp.method == :all or exp.method.to_s[0,4] == "find"
        models = Set.new @tracker.models.keys

        begin
          name = class_name target
          return target if models.include?(name)
        rescue StandardError
        end

      end

      return get_model_target(target)
    end

    false
  end

  #Ignore `<<` calls on template variables which are used by the templating
  #library (HAML, ERB, etc.)
  def find_push_target exp
    if sexp? exp
      if exp.node_type == :lvar and (exp.value == :_buf or exp.value == :_erbout)
        return nil
      elsif exp.node_type == :ivar and exp.value == :@output_buffer
        return nil
      elsif exp.node_type == :call and call? exp.target and
        exp.target.method == :_hamlout and exp.method == :buffer

        return nil
      end
    end

    super
  end
end