Brakeman.load_brakeman_dependency 'terminal-table'

class Brakeman::Report::Markdown < Brakeman::Report::Base

  class MarkdownTable < Terminal::Table

    def initialize options = {}, &block
      options[:style] ||= {}
      options[:style].merge!({
          :border_x => '-',
          :border_y => '|',
          :border_i => '|'
      })
      super options, &block
    end

    def render
      super.split("\n")[1...-1].join("\n")
    end
    alias :to_s :render

  end

  def generate_report
    out = "# BRAKEMAN REPORT\n\n" <<
    generate_metadata.to_s << "\n\n" <<
    generate_checks.to_s << "\n\n" <<
    "### SUMMARY\n\n" <<
    generate_overview.to_s << "\n\n" <<
    generate_warning_overview.to_s << "\n\n"

    #Return output early if only summarizing
    return out if tracker.options[:summary_only]

    if tracker.options[:report_routes] or tracker.options[:debug]
      out << "### CONTROLLERS"  << "\n\n" <<
      generate_controllers.to_s << "\n\n"
    end

    if tracker.options[:debug]
      out << "### TEMPLATES\n\n" <<
      generate_templates.to_s << "\n\n"
    end

    res = generate_errors
    out << "### Errors\n\n" << res.to_s << "\n\n" if res

    res = generate_warnings
    out << "### SECURITY WARNINGS\n\n" << res.to_s << "\n\n" if res

    res = generate_controller_warnings
    out << "### Controller Warnings:\n\n" << res.to_s << "\n\n" if res

    res = generate_model_warnings
    out << "### Model Warnings:\n\n" << res.to_s << "\n\n" if res

    res = generate_template_warnings
    out << "### View Warnings:\n\n" << res.to_s << "\n\n" if res

    out
  end

  def generate_metadata
    MarkdownTable.new(
      :headings =>
        ['Application path', 'Rails version', 'Brakeman version', 'Started at', 'Duration']
    ) do |t|
      t.add_row([
        tracker.app_path,
        rails_version,
        Brakeman::Version,
        tracker.start_time,
        "#{tracker.duration} seconds",
      ])
    end
  end

  def generate_checks
    MarkdownTable.new(:headings => ['Checks performed']) do |t|
      t.add_row([checks.checks_run.sort.join(", ")])
    end
  end

  def generate_overview
    num_warnings = all_warnings.length

    MarkdownTable.new(:headings => ['Scanned/Reported', 'Total']) do |t|
      t.add_row ['Controllers', tracker.controllers.length]
      t.add_row ['Models', tracker.models.length - 1]
      t.add_row ['Templates', number_of_templates(@tracker)]
      t.add_row ['Errors', tracker.errors.length]
      t.add_row ['Security Warnings', "#{num_warnings} (#{warnings_summary[:high_confidence]})"]
      t.add_row ['Ignored Warnings', ignored_warnings.length] unless ignored_warnings.empty?
    end
  end

  #Generate listings of templates and their output
  def generate_templates
    out_processor = Brakeman::OutputProcessor.new
    template_rows = {}
    tracker.templates.each do |name, template|
      template.each_output do |out|
        out = out_processor.format out
        template_rows[name] ||= []
        template_rows[name] << out.gsub("\n", ";").gsub(/\s+/, " ")
      end
    end

    template_rows = template_rows.sort_by{|name, value| name.to_s}

    output = ''
    template_rows.each do |template|
      output << template.first.to_s << "\n\n"
      table = MarkdownTable.new(:headings => ['Output']) do |t|
        # template[1] is an array of calls
        template[1].each do |v|
          t.add_row [v]
        end
      end

      output << table.to_s << "\n\n"
    end

    output
  end

  def render_array template, headings, value_array, locals
    return if value_array.empty?

    MarkdownTable.new(:headings => headings) do |t|
      value_array.each { |value_row| t.add_row value_row }
    end
  end

  def convert_warning warning, original
    warning["Confidence"] = TEXT_CONFIDENCE[warning["Confidence"]]
    warning["Message"] = markdown_message original, warning["Message"]
    warning["Warning Type"] = "[#{warning['Warning Type']}](#{original.link})" if original.link
    warning
  end

  # Escape and code format warning message
  def markdown_message warning, message
    if warning.file
      github_url = github_url warning.file, warning.line
      message.gsub!(/(near line \d+)/, "[\\1](#{github_url})") if github_url
    end
    if warning.code
      code = warning.format_code
      message.gsub(code, "`#{code.gsub('`','``').gsub(/\A``|``\z/, '` `')}`")
    else
      message
    end
  end

end
