   lines = lines.map (line) -> line.trim()
   lines.join('\n').trim()
 
+listen = (element, eventName, selector, handler) ->
+  innerHandler = (event) ->
+    if selector
+      currentTarget = event.target
+      loop
+        if currentTarget.matches and currentTarget.matches(selector)
+          handler({currentTarget: currentTarget, preventDefault: -> event.preventDefault()})
+
+        break if currentTarget is element
+        currentTarget = currentTarget.parentNode
+
+  element.addEventListener(eventName, innerHandler)
+
 module.exports =
-class AtomReporter extends View
-  @content: ->
-    @div class: 'spec-reporter', =>
-      @div class: 'padded pull-right', =>
-        @button outlet: 'reloadButton', class: 'btn btn-small reload-button', 'Reload Specs'
-      @div outlet: 'coreArea', class: 'symbol-area', =>
-        @div outlet: 'coreHeader', class: 'symbol-header'
-        @ul outlet: 'coreSummary', class: 'symbol-summary list-unstyled'
-      @div outlet: 'bundledArea', class: 'symbol-area', =>
-        @div outlet: 'bundledHeader', class: 'symbol-header'
-        @ul outlet: 'bundledSummary', class: 'symbol-summary list-unstyled'
-      @div outlet: 'userArea', class: 'symbol-area', =>
-        @div outlet: 'userHeader', class: 'symbol-header'
-        @ul outlet: 'userSummary', class: 'symbol-summary list-unstyled'
-      @div outlet: "status", class: 'status alert alert-info', =>
-        @div outlet: "time", class: 'time'
-        @div outlet: "specCount", class: 'spec-count'
-        @div outlet: "message", class: 'message'
-      @div outlet: "results", class: 'results'
-
-      @div outlet: "deprecations", class: 'status alert alert-warning', style: 'display: none', =>
-        @span outlet: 'deprecationStatus', '0 deprecations'
-        @div class: 'deprecation-toggle'
-      @div outlet: 'deprecationList', class: 'deprecation-list'
+class AtomReporter
+
+  constructor: ->
+    @element = document.createElement('div')
+    @element.innerHTML = """
+      <div class="spec-reporter">
+        <div class="padded pull-right">
+          <button outlet="reloadButton" class="btn btn-small reload-button">Reload Specs</button>
+        </div>
+        <div outlet="coreArea" class="symbol-area">
+          <div outlet="coreHeader" class="symbol-header"></div>
+          <ul outlet="coreSummary"class="symbol-summary list-unstyled"></ul>
+        </div>
+        <div outlet="bundledArea" class="symbol-area">
+          <div outlet="bundledHeader" class="symbol-header"></div>
+          <ul outlet="bundledSummary"class="symbol-summary list-unstyled"></ul>
+        </div>
+        <div outlet="userArea" class="symbol-area">
+          <div outlet="userHeader" class="symbol-header"></div>
+          <ul outlet="userSummary"class="symbol-summary list-unstyled"></ul>
+        </div>
+        <div outlet="status" class="status alert alert-info">
+          <div outlet="time" class="time"></div>
+          <div outlet="specCount" class="spec-count"></div>
+          <div outlet="message" class="message"></div>
+        </div>
+        <div outlet="results" class="results"></div>
+        <div outlet="deprecations" class="status alert alert-warning" style="display: none">
+          <span outlet="deprecationStatus">0 deprecations</span>
+          <div class="deprecation-toggle"></div>
+        </div>
+        <div outlet="deprecationList" class="deprecation-list"></div>
+      </div>
+    """
+
+    for element in @element.querySelectorAll('[outlet]')
+      this[element.getAttribute('outlet')] = element
 
   startedAt: null
   runningSpecCount: 0

  addDeprecations: (spec) ->
    deprecations = grim.getDeprecations()
    @deprecationCount += deprecations.length
    @deprecations.show() if @deprecationCount > 0
    if @deprecationCount is 1
      @deprecationStatus.text("1 deprecation")
    else
      @deprecationStatus.text("#{@deprecationCount} deprecations")

    for deprecation in deprecations
      @deprecationList.append $$ ->
        @div class: 'padded', =>
          @div class: 'result-message fail deprecation-message', =>
            @raw marked(deprecation.message)

          for stack in deprecation.getStacks()
            fullStack = stack.map ({functionName, location}) ->
              if functionName is '<unknown>'
                "  at #{location}"
              else
                "  at #{functionName} (#{location})"
            @pre class: 'stack-trace padded', formatStackTrace(spec, deprecation.message, fullStack.join('\n'))
    grim.clearDeprecations()

  handleEvents: ->
    $(document).on "click", ".spec-toggle", ({currentTarget}) ->
      element = $(currentTarget)
      specFailures = element.parent().find('.spec-failures')
      specFailures.toggle()
      element.toggleClass('folded')
      false

    $(document).on "click", ".deprecation-toggle", ({currentTarget}) ->
      element = $(currentTarget)
      deprecationList = $(document).find('.deprecation-list')
      deprecationList.toggle()
      element.toggleClass('folded')
      false

  updateSpecCounts: ->
    if @skippedCount
      specCount = "#{@completeSpecCount - @skippedCount}/#{@totalSpecCount - @skippedCount} (#{@skippedCount} skipped)"
    else
      specCount = "#{@completeSpecCount}/#{@totalSpecCount}"
    @specCount[0].textContent = specCount

  updateStatusView: (spec) ->
    if @failedCount > 0
      @status.addClass('alert-danger').removeClass('alert-info')

    @updateSpecCounts()

    rootSuite = spec.suite
    rootSuite = rootSuite.parentSuite while rootSuite.parentSuite
    @message.text rootSuite.description

    time = "#{Math.round((spec.endedAt - @startedAt) / 10)}"
    time = "0#{time}" if time.length < 3
    @time[0].textContent = "#{time[0...-2]}.#{time[-2..]}s"

  addSpecs: (specs) ->
    coreSpecs = 0
    bundledPackageSpecs = 0
    userPackageSpecs = 0
    for spec in specs
      symbol = $$ -> @li id: "spec-summary-#{spec.id}", class: "spec-summary pending"
      switch spec.specType
        when 'core'
          coreSpecs++
          @coreSummary.append symbol
        when 'bundled'
          bundledPackageSpecs++
          @bundledSummary.append symbol
        when 'user'
          userPackageSpecs++
          @userSummary.append symbol

    if coreSpecs > 0
      @coreHeader.text("Core Specs (#{coreSpecs})")
    else
      @coreArea.hide()
    if bundledPackageSpecs > 0
      @bundledHeader.text("Bundled Package Specs (#{bundledPackageSpecs})")
    else
      @bundledArea.hide()
    if userPackageSpecs > 0
      if coreSpecs is 0 and bundledPackageSpecs is 0
        # Package specs being run, show a more descriptive label
        {specDirectory} = specs[0]
        packageFolderName = path.basename(path.dirname(specDirectory))
        packageName = _.undasherize(_.uncamelcase(packageFolderName))
        @userHeader.text("#{packageName} Specs")
      else
        @userHeader.text("User Package Specs (#{userPackageSpecs})")
    else
      @userArea.hide()

  specStarted: (spec) ->
    @runningSpecCount++

  specComplete: (spec) ->
    specSummaryElement = $("#spec-summary-#{spec.id}")
    specSummaryElement.removeClass('pending')

    results = spec.results()
    if results.skipped
      specSummaryElement.addClass("skipped")
      @skippedCount++
    else if results.passed()
      specSummaryElement.addClass("passed")
      @passedCount++
    else
      specSummaryElement.addClass("failed")

      specView = new SpecResultView(spec)
      specView.attach()
      @failedCount++
    @addDeprecations(spec)

class SuiteResultView extends View
  @content: ->
    @div class: 'suite', =>
      @div outlet: 'description', class: 'description'

  initialize: (@suite) ->
    @attr('id', "suite-view-#{@suite.id}")
    @description.text(@suite.description)

  attach: ->
    (@parentSuiteView() or $('.results')).append this

  parentSuiteView: ->
    return unless @suite.parentSuite

    if not suiteView = $("#suite-view-#{@suite.parentSuite.id}").view()
      suiteView = new SuiteResultView(@suite.parentSuite)
      suiteView.attach()

    suiteView

class SpecResultView extends View
  @content: ->
    @div class: 'spec', =>
      @div class: 'spec-toggle'
      @div outlet: 'description', class: 'description'
      @div outlet: 'specFailures', class: 'spec-failures'

  initialize: (@spec) ->
    @addClass("spec-view-#{@spec.id}")

    description = @spec.description
    description = "it #{description}" if description.indexOf('it ') isnt 0
    @description.text(description)

    for result in @spec.results().getItems() when not result.passed()
      stackTrace = formatStackTrace(@spec, result.message, result.trace.stack)
      @specFailures.append $$ ->
        @div result.message, class: 'result-message fail'
        @pre stackTrace, class: 'stack-trace padded' if stackTrace

  attach: ->
    @parentSuiteView().append this

  parentSuiteView: ->
    if not suiteView = $("#suite-view-#{@spec.suite.id}").view()
      suiteView = new SuiteResultView(@spec.suite)
      suiteView.attach()

    suiteView

    specs = runner.specs()
    @totalSpecCount = specs.length
    @addSpecs(specs)
    document.body.appendChild(@element)

  reportRunnerResults: (runner) ->
    @updateSpecCounts()
    if @failedCount is 0
      @status.classList.add('alert-success')
      @status.classList.remove('alert-info')

    if @failedCount is 1
      @message.textContent = "#{@failedCount} failure"
    else
      @message.textConent = "#{@failedCount} failures"

  reportSuiteResults: (suite) ->

