<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title><%= title %></title>
    <style>
      * { box-sizing: border-box; }
      body {
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
        font-size: 13px;
        padding: 0;
        margin: 0;
        background: #f5f5f5;
        color: #333;
      }

      ul {
        margin: 0;
        padding-left: 0;
        list-style-type: none;
      }

      li {
        margin-left: 20px;
        padding: 2px 4px;
        white-space: nowrap;
        border-left: 2px solid transparent;
        border-radius: 2px;
      }

      li:hover {
        background: rgba(0,0,0,0.03);
      }

      .thread {
        margin: 0 16px;
        background: #fff;
        padding: 8px 12px;
        border-radius: 6px;
        border: 1px solid #e0e0e0;
        margin-bottom: 12px;
        font-weight: 600;
        color: #555;
      }

      .hidden {
        display: none;
        width: 0;
        height: 0;
        margin: 0;
        padding: 0;
        border-style: none;
      }

      .color01 { border-left-color: #90CAF9; background: #F5F9FF; }
      .color05 { border-left-color: #64B5F6; background: #EEF5FF; }
      .color0  { border-left-color: #42A5F5; background: #E8F1FF; }
      .color1  { border-left-color: #26C6DA; background: #E6F9FB; }
      .color2  { border-left-color: #26A69A; background: #E4F5F3; }
      .color3  { border-left-color: #66BB6A; background: #E8F5E9; }
      .color4  { border-left-color: #9CCC65; background: #F1F8E9; }
      .color5  { border-left-color: #D4E157; background: #F9FBE7; }
      .color6  { border-left-color: #FFA726; background: #FFF3E0; }
      .color7  { border-left-color: #FF7043; background: #FBE9E7; }
      .color8  { border-left-color: #EF5350; background: #FFEBEE; }
      .color9  { border-left-color: #E53935; background: #FFEBEE; }

      #header {
        background: #0D2483;
        color: #fff;
        padding: 1rem 1.5rem;
        display: flex;
        align-items: center;
        gap: 16px;
        flex-wrap: wrap;
      }

      #header .header-left {
        display: flex;
        flex-direction: column;
        gap: 2px;
      }

      #header h6 {
        margin: 0;
        font-size: 0.75rem;
        text-transform: uppercase;
        letter-spacing: 1.5px;
        color: rgba(255, 255, 255, 0.6);
        font-weight: 600;
      }

      #header h1 {
        margin: 0;
        font-size: 22px;
        font-weight: 600;
        white-space: nowrap;
      }

      .logo {
        width: 140px;
        height: 30px;
        opacity: 0.5;
        background-repeat: no-repeat;
        background-image: url("data:image/svg+xml,%3Csvg id='Layer_1' data-name='Layer 1' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 190 41'%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill:%23fff%7D%3C/style%3E%3C/defs%3E%3Cpath class='cls-1' d='M63.49 16.74c0-1.37-.91-2-2.26-2h-3.38v3.93h3.58c1.24.01 2.06-.56 2.06-1.93zM110.43 14.75h-3.6v5h3.6a2.32 2.32 0 0 0 2.5-2.49 2.34 2.34 0 0 0-2.5-2.51zM128.77 14.75h-3.53v4.94h3.53a2.28 2.28 0 0 0 2.47-2.45 2.3 2.3 0 0 0-2.47-2.49zM61.81 21.83h-4v4.32h3.91c1.44 0 2.45-.62 2.45-2.14s-.99-2.18-2.36-2.18zM21 14.75h-3.57v4.94H21a2.28 2.28 0 0 0 2.47-2.45A2.3 2.3 0 0 0 21 14.75z'/%3E%3Cpath class='cls-1' d='M184 .44H5.87A4.93 4.93 0 0 0 .94 5.37V35.5a4.94 4.94 0 0 0 4.93 4.94H184a4.94 4.94 0 0 0 4.94-4.94V5.37A4.94 4.94 0 0 0 184 .44zm-34.38 10.78c4.79 0 8.46 2.89 9.18 7.54h-3.86a5.48 5.48 0 0 0-10.68 0h-3.83c.71-4.65 4.36-7.54 9.19-7.54zM24 29.44L20.46 23h-3v6.46h-3.72v-18h7.68c3.41 0 5.78 2.07 5.78 5.76a5.34 5.34 0 0 1-2.88 5.14l3.82 7.06zm24.31-7.3c0 4.8-2.92 7.56-7.63 7.56S33 26.94 33 22.14V11.48h3.69v10.61c0 2.81 1.37 4.32 4 4.32s3.94-1.51 3.94-4.32V11.48h3.69zm13.92 7.3h-8v-18h7.52c3.24 0 5.59 1.61 5.59 5A4 4 0 0 1 65.51 20 4.22 4.22 0 0 1 68 24.21c0 3.43-2.4 5.23-5.81 5.23zm19.66-6.92v6.92h-3.7v-6.92l-6.81-11h4.29L80 18.85l4.34-7.37h4.32zm16-.5h-7.62v-3.26h7.59zm13 1h-4v6.39h-3.72v-18h7.77c3.41 0 5.79 2.09 5.79 5.79s-2.41 5.85-5.82 5.85zm20.86 6.39L128.26 23h-3v6.46h-3.72v-18h7.69c3.4 0 5.78 2.07 5.78 5.76a5.34 5.34 0 0 1-2.88 5.14l3.87 7.08zm17.85.26c-4.88 0-8.55-2.95-9.21-7.68h3.81A5.49 5.49 0 0 0 155 22h3.84c-.69 4.75-4.38 7.7-9.22 7.7zm18.4-.23h-3.72v-3.7H168zm6.36-7.54h-10.05v-3.26h10.08zm1.44-7.16h-11.49v-3.26h11.52z'/%3E%3C/svg%3E");
      }

      #commands {
        background: #0D2483;
        padding: 8px 1.5rem 12px 1.5rem;
        display: flex;
        align-items: center;
        gap: 10px;
        flex-wrap: wrap;
      }

      #commands label {
        font-size: 13px;
        color: rgba(255, 255, 255, 0.6);
        font-weight: 600;
      }

      #commands input[type="text"] {
        font-size: 13px;
        padding: 4px 8px;
        border: 1px solid rgba(255, 255, 255, 0.3);
        border-radius: 3px;
        background: rgba(255, 255, 255, 0.15);
        color: #fff;
        width: 50px;
      }

      #commands button {
        font-size: 13px;
        padding: 4px 12px;
        border: 1px solid rgba(255, 255, 255, 0.3);
        border-radius: 3px;
        background: rgba(255, 255, 255, 0.15);
        color: #fff;
        cursor: pointer;
      }

      #commands button:hover {
        background: rgba(255, 255, 255, 0.25);
      }

      #help {
        font-size: 13px;
        padding: 12px 20px;
        margin: 0;
        background: #fff;
        display: none;
        border-bottom: 1px solid #e0e0e0;
        color: #555;
      }

      #help li {
        margin-left: 0;
        padding: 2px 0;
        border-left: none;
      }

      #content {
        padding: 16px;
      }

      #sentinel {
        height: 300px;
      }

      a {
        color: #0984e3;
        text-decoration: none;
      }

      a:hover {
        text-decoration: underline;
      }

      .toggle {
        display: inline-block;
        width: 16px;
        height: 16px;
        text-align: center;
        line-height: 16px;
        cursor: pointer;
        font-size: 10px;
        color: #999;
        vertical-align: middle;
        margin-right: 2px;
        border-radius: 2px;
        user-select: none;
      }

      .toggle:hover {
        background: rgba(0,0,0,0.08);
        color: #333;
      }

      .toggle.minus::before { content: "\25BE"; }
      .toggle.plus::before { content: "\25B8"; }
      .toggle.empty { visibility: hidden; }
    </style>

    <script type="text/javascript">
      function rootNode()
      {
        return currentThread
      }

      function showUL(node, show)
      {
        Array.prototype.forEach.call(node.childNodes, function(child)
        {
          if (child.nodeName == 'LI')
            toggle(child, show)
        })
      }

      function findUlChild(li)
      {
        var ul = li.childNodes[2]
        while (ul && ul.nodeName != "UL")
        {
          ul = ul.nextSibling
        }
        return ul
      }

      function isLeafNode(li)
      {
        var element = li.querySelector('.toggle')
        return element && element.classList.contains('empty')
      }

      function toggle(li, show)
      {
        if (isLeafNode(li))
          return

        var img = li.firstChild
        img.className = 'toggle '
        img.className += show ? 'minus' : 'plus'

        var ul = findUlChild(li)
        if (ul)
        {
          ul.style.display = show ? 'block' : 'none'
          showUL(ul, true)
        }
      }

      function toggleLI(li)
      {
        var img = li.firstChild
        if (img.className.indexOf("minus") > -1)
          toggle(li, false)
        else
        {
          if (img.className.indexOf("plus") > -1)
            toggle(li, true)
        }
      }

      function aboveThreshold(text, threshold)
      {
        var match = text.match(/\d+[.,]\d+%/)
        if (!match)
        {
          return true
        }
        else
        {
          var value = parseFloat(match[0].replace(/,/, '.'))
          return value >= threshold
        }
      }

      function setThresholdLI(li, threshold)
      {
        var a = li.querySelector('.toggle')
        var span = li.querySelector('span')
        var ul = li.querySelector('ul')

        var visible = aboveThreshold(span.textContent, threshold) ? 1 : 0

        var count = 0
        if (ul)
        {
          count = setThresholdUL(ul, threshold)
        }

        if (count > 0)
        {
          a.className = 'toggle minus'
        }
        else
        {
          a.className = 'toggle empty'
        }

        if (visible)
        {
          li.style.display = 'block'
        } else
        {
          li.style.display = 'none'
        }
        return visible
      }

      function setThresholdUL(node, threshold)
      {
        var count = 0
        Array.prototype.forEach.call(node.childNodes, function(child)
        {
          if (child.nodeName == 'LI')
            count = count + setThresholdLI(child, threshold)
        })

        var visible = (count > 0) ? 1 : 0
        if (visible)
        {
          node.style.display = 'block'
        } else
        {
          node.style.display = 'none'
        }
        return visible
      }

      function toggleChildren(img, event)
      {
        event.cancelBubble = true
        if (img.className.indexOf('empty') > -1)
          return

        var minus = (img.className.indexOf('minus') > -1)

        if (minus)
        {
          img.className = 'toggle plus'
        } else
          img.className = 'toggle minus'

        var li = img.parentNode
        var ul = findUlChild(li)
        if (ul)
        {
          if (minus)
            ul.style.display = 'none'
          else
            ul.style.display = 'block'
        }
        if (minus)
          moveSelectionIfNecessary(li)
      }

      function showChildren(li)
      {
        var img = li.firstChild
        if (img.className.indexOf('empty') > -1)
          return
        img.className = 'toggle minus'

        var ul = findUlChild(li)
        if (ul)
        {
          ul.style.display = 'block'
        }
      }

      function setThreshold()
      {
        var tv = document.getElementById("threshold").value
        if (tv.match(/[0-9]+([.,][0-9]+)?/))
        {
          var f = parseFloat(tv.replace(/,/, '.'))
          var threads = document.getElementsByName("thread")
          var l = threads.length
          for (var i = 0; i < l; i++)
          {
            setThresholdUL(threads[i], f)
          }
          var p = selectedNode
          while (p && p.style.display == 'none')
            p = p.parentNode.parentNode
          if (p && p.nodeName == "LI")
            selectNode(p)
        } else
        {
          alert("Please specify a decimal number as threshold value!")
        }
      }

      function expandAll(event)
      {
        toggleAll(event, true)
      }

      function collapseAll(event)
      {
        toggleAll(event, false)
        selectNode(rootNode(), null)
      }

      function toggleAll(event, show)
      {
        event.cancelBubble = true
        var threads = document.getElementsByName("thread")
        var l = threads.length
        for (var i = 0; i < l; i++)
        {
          showUL(threads[i], show)
        }
      }

      function toggleHelp(btn)
      {
        var help = document.getElementById("help")
        if (help.style.display === 'block')
        {
          btn.textContent = "Help"
          help.style.display = 'none'
        } else
        {
          btn.textContent = "Hide Help"
          help.style.display = 'block'
        }
      }

      var selectedNode = null
      var selectedColor = null
      var selectedThread = null

      function descendentOf(a, b)
      {
        while (a != b && b != null)
          b = b.parentNode
        return (a == b)
      }

      function moveSelectionIfNecessary(node)
      {
        if (descendentOf(node, selectedNode))
          selectNode(node, null)
      }

      function selectNode(node, event)
      {
        if (event)
        {
          event.cancelBubble = true
          thread = findThread(node)
          selectThread(thread)
        }
        if (selectedNode)
        {
          selectedNode.style.background = selectedColor
        }
        selectedNode = node
        selectedColor = node.style.background
        selectedNode.style.background = "#BBDEFB"
        selectedNode.scrollIntoView()
        window.scrollBy(0, -400)
      }

      function moveUp()
      {
        move(selectedNode.previousSibling)
      }

      function moveDown()
      {
        move(selectedNode.nextSibling)
      }

      function move(p)
      {
        while (p && p.style.display == 'none')
          p = p.nextSibling
        if (p && p.nodeName == "LI")
        {
          selectNode(p, null)
        }
      }

      function moveLeft()
      {
        var p = selectedNode.parentNode.parentNode
        if (p && p.nodeName == "LI")
        {
          selectNode(p, null)
        }
      }

      function moveRight()
      {
        if (!isLeafNode(selectedNode))
        {
          showChildren(selectedNode)
          var ul = findUlChild(selectedNode)
          if (ul)
          {
            selectNode(ul.firstChild, null)
          }
        }
      }

      function moveForward()
      {
        if (isLeafNode(selectedNode))
        {
          var p = selectedNode
          while ((p.nextSibling == null || p.nextSibling.style.display == 'none') && p.nodeName == "LI")
          {
            p = p.parentNode.parentNode
          }
          if (p.nodeName == "LI")
            selectNode(p.nextSibling, null)
        } else
        {
          moveRight()
        }
      }

      function isExpandedNode(li)
      {
        var img = li.firstChild
        return (img.className.indexOf('minus') > -1)
      }

      function moveBackward()
      {
        var p = selectedNode
        var q = p.previousSibling
        while (q != null && q.style.display == 'none')
          q = q.previousSibling
        if (q == null)
        {
          p = p.parentNode.parentNode
        } else
        {
          while (!isLeafNode(q) && isExpandedNode(q))
          {
            q = findUlChild(q).lastChild
            while (q.style.display == 'none')
              q = q.previousSibling
          }
          p = q
        }
        if (p.nodeName == "LI")
          selectNode(p, null)
      }

      function moveHome()
      {
        selectNode(currentThread)
      }

      var currentThreadIndex = null

      function findThread(node)
      {
        while (node && !node.parentNode.nodeName.match(/BODY|DIV/g))
        {
          node = node.parentNode
        }
        return node.firstChild
      }

      function selectThread(node)
      {
        var threads = document.getElementsByName("thread")
        currentThread = node
        for (var i = 0; i < threads.length; i++)
        {
          if (threads[i] == currentThread.parentNode)
            currentThreadIndex = i
        }
      }

      function nextThread()
      {
        var threads = document.getElementsByName("thread")
        if (currentThreadIndex == threads.length - 1)
          currentThreadIndex = 0
        else
          currentThreadIndex += 1
        currentThread = threads[currentThreadIndex].firstChild
        selectNode(currentThread, null)
      }

      function previousThread()
      {
        var threads = document.getElementsByName("thread")
        if (currentThreadIndex == 0)
          currentThreadIndex = threads.length - 1
        else
          currentThreadIndex -= 1
        currentThread = threads[currentThreadIndex].firstChild
        selectNode(currentThread, null)
      }

      function switchThread(node, event)
      {
        event.cancelBubble = true
        selectThread(node.nextSibling.firstChild)
        selectNode(currentThread, null)
      }

      function handleKeyEvent(event)
      {
        var code = event.charCode ? event.charCode : event.keyCode
        var str = String.fromCharCode(code)
        switch (str)
        {
          case "a":
            moveLeft()
            break
          case "s":
            moveDown()
            break
          case "d":
            moveRight()
            break
          case "w":
            moveUp()
            break
          case "f":
            moveForward()
            break
          case "b":
            moveBackward()
            break
          case "x":
            toggleChildren(selectedNode.firstChild, event)
            break
          case "*":
            toggleLI(selectedNode)
            break
          case "n":
            nextThread()
            break
          case "h":
            moveHome()
            break
          case "p":
            previousThread()
            break
        }
      }

      document.onkeypress = function (event)
      {
        handleKeyEvent(event)
      }

      window.onload = function ()
      {
        var toggles = document.querySelectorAll(".toggle")
        for (var i = 0; i < toggles.length; i++)
        {
          var tog = toggles[i]
          tog.onclick = function (event)
          {
            toggleChildren(this, event)
            return false
          }
        }
        var divs = document.getElementsByTagName("div")
        for (i = 0; i < divs.length; i++)
        {
          var div = divs[i]
          if (div.className == "thread")
            div.onclick = function (event)
            {
              switchThread(this, event)
            }
        }
        var lis = document.getElementsByTagName("li")
        for (var i = 0; i < lis.length; i++)
        {
          lis[i].onclick = function (event)
          {
            selectNode(this, event)
          }
        }

        var threads = document.getElementsByName("thread")
        currentThreadIndex = 0
        currentThread = threads[0].querySelector('li')
        selectNode(currentThread, null)
      }
    </script>

    <% @overall_time = @result.threads.reduce(0) do |val, thread|
      val += thread.total_time
    end %>
  </head>
  <body>
    <div id="header">
      <div class="header-left">
        <h6>Call Stack Report</h6>
        <h1><%= @result.measure_mode_name %></h1>
      </div>
      <div style="margin-left: auto; display: flex; align-items: flex-end; flex-direction: column;">
        <div style="font-size: 12px; margin-bottom: 0.5rem; color: rgba(255,255,255,0.6);"><%= Time.now.strftime(time_format) %></div>
        <div class="logo"></div>
      </div>
    </div>
    <div id="commands">
      <label for="threshold">Threshold:</label>
      <input value="1.0" size="3" id="threshold" type="text">
      <button onclick="setThreshold();">Apply</button>
      <button onclick="expandAll(event);">Expand All</button>
      <button onclick="collapseAll(event);">Collapse All</button>
      <button onclick="toggleHelp(this);">Help</button>
    </div>
    <ul id="help">
      <li>* indicates recursively called methods</li>
      <li>Enter a threshold value and click "Apply" to hide methods below that percentage.</li>
      <li>Use <strong>a, s, d, w</strong> to navigate the tree (left, down, right, up).</li>
      <li>Use <strong>f</strong> and <strong>b</strong> to navigate forward and backwards in preorder.</li>
      <li>Use <strong>x</strong> to toggle a subtree, <strong>*</strong> to expand/collapse a whole subtree.</li>
      <li>Use <strong>h</strong> to navigate to thread root, <strong>n</strong> and <strong>p</strong> to switch threads.</li>
    </ul>

    <div id="content">
      <% @result.threads.each do |thread| %>
        <% thread_percent = 100 * (thread.total_time / @overall_time)
           thread_info = "#{"%4.2f%%" % thread_percent} ~ #{@overall_time}" %>
        <div class="thread">
          <span>Thread: <%= thread.id %>, Fiber: <%= thread.fiber_id %> (<%= thread_info %>)</span>
          <ul name="thread">
            <% visited = Set.new
               output = StringIO.new
               print_stack(output, visited, thread.call_tree, thread.call_tree.total_time) %>
            <%= output.string %>
          </ul>
        </div>
      <% end %>
      <div id="sentinel"></div>
    </div>
  </body>
</html>
