<!--
    Copyright (c) 2020 Contributors to the Eclipse Foundation

    See the NOTICE file(s) distributed with this work for additional
    information regarding copyright ownership.

    This program and the accompanying materials are made available under the
    terms of the Eclipse Public License 2.0 which is available at
    http://www.eclipse.org/legal/epl-2.0

    SPDX-License-Identifier: EPL-2.0
 -->
<template>
  <el-table ref="table" :data="threads"
            :highlight-current-row="false"
            stripe
            :header-cell-style="headerCellStyle"
            :cell-style='cellStyle'
            row-key="rowKey"
            lazy
            v-loading="loading"
            height="100%"
            :indent=8
            :load="loadStackInfo"
            :span-method="tableSpanMethod">
    <el-table-column label="Thread / Stack" width="450px" show-overflow-tooltip>
      <template slot-scope="scope">
          <span v-if="scope.row.isThread" @click="$emit('setSelectedObjectId', scope.row.objectId)"
                style="cursor: pointer">
            <img :src="threadIcon"/> {{scope.row.object}}
          </span>

        <span v-if="scope.row.isStack">
            <img :src="frameIcon"/> {{scope.row.stack}}
          </span>

        <span v-if="scope.row.isLocal" @click="$emit('setSelectedObjectId', scope.row.objectId)"
              style="cursor: pointer">
            <img :src="scope.row.icon">
            <strong>{{ " " +scope.row.prefix + " "}}</strong>
            {{scope.row.label}}
            <span style="font-weight: bold; color: #909399">{{ " " + scope.row.suffix}}</span>
          </span>

        <span v-if="scope.row.isOutbound" @click="$emit('setSelectedObjectId', scope.row.objectId)"
              style="cursor: pointer">
            <img :src="scope.row.icon">
            <strong>{{ " " +scope.row.prefix + " "}}</strong>
            {{scope.row.label}}
            <span style="font-weight: bold; color: #909399">{{ " " + scope.row.suffix}}</span>
          </span>

        <span v-if="scope.row.isOutboundsSummary">
            <img :src="ICONS.misc.sumIcon" v-if="scope.row.currentSize >= scope.row.totalSize"/>

            <img :src="ICONS.misc.sumPlusIcon"
                 @dblclick="fetchOutbounds(scope.row.parentRowKey, scope.row.objectId, scope.row.nextPage, scope.row.resolve)"
                 style="cursor: pointer"
                 v-else/>
            {{ scope.row.currentSize }} <strong> / </strong> {{ scope.row.totalSize }}
          </span>

        <span v-if="scope.row.isThreadSummaryItem">
            <img :src="sumIcon" v-if="currentSize >= totalSize"/>
            <img :src="sumPlusIcon" @dblclick="fetchThreadDetails" style="cursor: pointer" v-else/>
            {{ currentSize }} <strong> / </strong> {{totalSize}}
          </span>
      </template>
    </el-table-column>

    <el-table-column label="Name" prop="name" width="300px" show-overflow-tooltip>
    </el-table-column>
    <el-table-column label="Shallow Heap" prop="shallowHeap">
    </el-table-column>
    <el-table-column label="Retained Heap" prop="retainedHeap">
    </el-table-column>
    <el-table-column label="Context Class Loader" prop="contextClassLoader" width="420px" show-overflow-tooltip>
    </el-table-column>
    <el-table-column label="Is Daemon" prop="daemon">
    </el-table-column>
  </el-table>
</template>

<script>
  import axios from 'axios'
  import {heapDumpService} from '../../util'
  import {getOutboundIcon, ICONS} from './IconHealper'

  let rowKey = 1

  export default {
    props: ['file'],
    methods: {
      loadStackInfo(tree, treeNode, resolve) {
        if (tree.isThread) {
          this.fetchStackTrace(tree.objectId, resolve)
        } else if (tree.isStack) {
          this.fetchLocals(tree.threadObjectId, tree.depth, tree.firstNonNativeFrame, resolve)
        } else if (tree.isLocal || tree.isOutbound) {
          this.fetchOutbounds(tree.rowKey, tree.objectId, 1, resolve)
        } else if (tree.isOutboundsSummary) {
          this.fetchOutbounds(tree.parentRowKey, tree.objectId, tree.nextPage, resolve)
        }
      },
      tableSpanMethod(i) {
        if (i.row.isStack) {
          if (i.columnIndex === 0) {
            return [1, 2];
          } else if (i.columnIndex === 1) {
            return [0, 0];
          } else {
            return [1, 1]
          }
        }

        if (i.row.isLocal || i.row.isOutbound) {
          if (i.columnIndex === 0) {
            return [1, 2];
          } else if (i.columnIndex === 1) {
            return [0, 0];
          } else {
            return [1, 1]
          }
        }
      },
      fetchOutbounds(parentRowKey, objectId, page, resolve) {
        this.loading = true
        axios.get(heapDumpService(this.file, 'outbounds'), {
          params: {
            objectId: objectId,
            page: page,
            pageSize: this.pageSize,
          }
        }).then(resp => {
          let loadedLen = 0;
          let loaded = this.$refs['table'].store.states.lazyTreeNodeMap[parentRowKey]
          let callResolve = false
          if (loaded) {
            loadedLen = loaded.length
            if (loadedLen > 0) {
              loaded.splice(--loadedLen, 1)
            }
          } else {
            loaded = []
            callResolve = true;
          }
          let res = resp.data.data
          for (let i = 0; i < res.length; i++) {
            loaded.push({
              rowKey: rowKey++,
              isOutbound: true,
              objectId: res[i].objectId,
              prefix: res[i].prefix,
              label: res[i].label,
              suffix: res[i].suffix,
              shallowHeap: res[i].shallowSize,
              retainedHeap: res[i].retainedSize,
              icon: getOutboundIcon(res[i].gCRoot, res[i].objectType),
              hasChildren: res[i].hasOutbound
            })
          }

          loaded.push({
            rowKey: rowKey++,
            objectId: objectId,
            parentRowKey: parentRowKey,
            isOutboundsSummary: true,
            nextPage: page + 1,
            currentSize: loadedLen + res.length,
            totalSize: resp.data.totalSize,
            resolve: resolve,
          })

          if (callResolve) {
            resolve(loaded)
          }
          this.loading = false
        })

      },
      fetchLocals(objId, depth, firstNonNativeFrame, resolve) {
        this.loading = true
        axios.get(heapDumpService(this.file, 'locals'), {
          params: {
            objectId: objId,
            depth: depth,
            firstNonNativeFrame: firstNonNativeFrame
          }
        }).then(resp => {
          let res = resp.data
          let locals = []
          for (let i = 0; i < res.length; i++) {
            locals.push({
              rowKey: rowKey++,
              isLocal: true,
              objectId: res[i].objectId,
              prefix: res[i].prefix,
              label: res[i].label,
              suffix: res[i].suffix,
              shallowHeap: res[i].shallowSize,
              retainedHeap: res[i].retainedSize,
              icon: getOutboundIcon(res[i].gCRoot, res[i].objectType),
              hasChildren: res[i].hasOutbound
            })
          }
          resolve(locals)
          this.loading = false
        })
      },
      fetchStackTrace(objId, resolve) {
        this.loading = true
        axios.get(heapDumpService(this.file, 'stackTrace'), {
          params: {
            objectId: objId
          }
        }).then(resp => {
          let res = resp.data
          let stacks = []
          let depth = 1
          for (let i = 0; i < res.length; i++) {
            stacks.push({
              rowKey: rowKey++,
              isStack: true,
              threadObjectId: objId,
              stack: res[i].stack,
              depth: depth++,
              hasChildren: res[i].hasLocal,
              firstNonNativeFrame: res[i].firstNonNativeFrame
            })
          }
          resolve(stacks)
          this.loading = false
        })
      },
      fetchThreadsData() {
        this.loading = true
        axios.get(heapDumpService(this.file, 'threadsSummary'), {}).then(resp => {
          this.shallowHeap = resp.data.shallowHeap
          this.retainedHeap = resp.data.retainedHeap
          this.totalSize = resp.data.totalSize
          this.fetchThreadDetails();
        })
      },
      fetchThreadDetails() {
        if (this.currentSize >= this.totalSize) {
          return
        }
        this.loading = true
        if (this.nextPage > 1) {
          this.threads.splice(this.threads.length - 1, 1)
        }
        axios.get(heapDumpService(this.file, 'threads'), {
          params: {
            page: this.nextPage,
            pageSize: this.pageSize
          }
        }).then(resp => {
          let res = resp.data.data
          let tmp = []
          this.currentSize += res.length
          for (let i = 0; i < res.length; i++) {
            tmp.push({
              rowKey: rowKey++,
              isThread: true,
              objectId: res[i].objectId,
              object: res[i].object,
              name: res[i].name,
              shallowHeap: res[i].shallowSize,
              retainedHeap: res[i].retainedSize,
              contextClassLoader: res[i].contextClassLoader,
              daemon: res[i].daemon + '',
              hasChildren: res[i].hasStack
            })
          }
          this.nextPage++
          tmp.forEach(t => this.threads.push(t))
          this.threads.push({
            rowKey: rowKey++,
            isThreadSummaryItem: true,
            shallowHeap: this.shallowHeap,
            retainedHeap: this.retainedHeap
          })
          this.loading = false
        })
      }
    },
    data() {
      return {
        loading: false,
        threads: [],
        ICONS,
        threadIcon: require('../../assets/heap/thread.gif'),
        frameIcon: require('../../assets/heap/stack_frame.gif'),
        sumIcon: require('../../assets/heap/misc/sum.gif'),
        sumPlusIcon: require('../../assets/heap/misc/sum_plus.gif'),
        nextPage: 1,
        pageSize: 25,
        currentSize: 0,
        totalSize: 0,
        shallowHeap: 0,
        retainedHeap: 0,
        cellStyle: {padding: '4px', fontSize: '12px'},
        headerCellStyle: {padding: 0, 'font-size': '12px', 'font-weight': 'normal'}
      }
    },
    created() {
      this.fetchThreadsData()
    }
  }
</script>