<template>
  <div
    class="bio-model-tree panel-section py-0"
    data-test="bio-model-tree">

    <v-container
      class=""
      grid-list-md
      text-xs-center>

      <v-layout row wrap>
        <v-flex xs12>

          <!-- Hide Tree Button -->
          <div
            md4 layout justify-end align-start ma-0 pt-2
            class="panel-slot-actions ">

            <v-tooltip top>
              <v-icon
                slot="activator"
                small
                class="primary--text pull-right"
                @click="$emit('hide-tree')">
                arrow_drop_down
              </v-icon>
              <span>Hide Individual Tree</span>
            </v-tooltip>
          </div>

          <!-- Graph -->
          <div class="individual-tree-div">
            <div class="individual-tree" />
          </div>

        </v-flex>
      </v-layout>
    </v-container>
  </div>

</template>

<script>
/*
  Vue implementation of D3 Tree from:
  https://beta.observablehq.com/@asktree/interactive-tree-diagram-d3v4-v5
*/

import { mapGetters } from 'vuex'
import {
  tree as d3Tree,
  select as d3Select,
  hierarchy as d3Hierarchy
} from 'd3'

import BasePanel from '@/components/base/BasePanel.vue'
import { fetchIndividualTree } from '@/utils/api'
import { SHOW_PANEL } from '@/store/actions/panels'

export default {
  name: 'BioModelTree',
  components: {
    BasePanel
  },
  data () {
    return {
      loading: false,
      duration: 0,
      data: null,
      rootNode: null,
      tree: null,
      graph: null,
      species: null,
      dataNodes: null
    }
  },
  computed: {
    ...mapGetters({
      currentModel: 'bioModelId',
      individual: 'individualId'
    })
  },
  watch: {
    individual (value, oldValue) {
      if (value !== oldValue) {
        this.fetchData()
      }
    },
    currentModel (value) {
      this.data && this.update(this.data)
    }
  },
  mounted () {
    this.fetchData()
  },
  methods: {
    buildTree () {
      let margin = {
        top: 0,
        bottom: 0,
        right: 5,
        left: 80
      }
      let width = 500 - margin.right - margin.left
      let height = 30 * this.dataNodes - margin.top - margin.bottom

      this.tree = d3Tree().size([height, width])

      let svg = d3Select(this.$el).select('.individual-tree')
      svg.selectAll('svg').remove()

      this.graph = svg
        .append('svg')
        .attr('width', width + margin.right + margin.left)
        .attr('height', height + margin.top + margin.bottom)
        .append('g')
        .attr('transform', `translate(${margin.left},${margin.top})`)

      this.rootNode = d3Hierarchy(this.data, d => d.children)
      this.rootNode.x0 = height / 2
      this.rootNode.y0 = 0

      this.update(this.rootNode)
    },
    async fetchData () {
      if (this.individual) {
        this.loading = true
        let individual = await fetchIndividualTree(this.individual)
        this.species = individual.species

        // Format Individual Tree
        let individualNode = {}
        individualNode.name = individual.system_id
        individualNode.parent = null
        individualNode.class = 'individual'
        individualNode.children = individual.sample_set.map(sample => {
          // Samples Children
          let sampleNode = {}
          sampleNode.name = sample.system_id
          sampleNode.parent = individual.system_id
          sampleNode.class = 'sample'
          sampleNode.children = sample.experiment_set.map(experiment => {
            // Experiment Children
            let experimentNode = {}
            experimentNode.name = experiment.system_id
            experimentNode.parent = sample.system_id
            experimentNode.class = 'experiment'
            return experimentNode
          })
          return sampleNode
        })

        this.data = individualNode
        this.dataNodes = individualNode.children.reduce((totalNodes, nodeInChildren) => {
          return totalNodes + nodeInChildren.children.length
        }, 0)

        this.buildTree()
        this.loading = false
      }
    },
    update (source) {
      let data = this.tree(this.rootNode)
      let i = 0

      // Compute the new tree layout.
      let nodes = data.descendants()
      let links = data.descendants().slice(1)

      // Normalize for fixed-depth.
      nodes.forEach(d => { d.y = d.depth * 180 })

      // ****************** Nodes section ***************************

      // Update the nodes...
      let node = this.graph.selectAll('g.node')
        .data(nodes, d => { return d.id || (d.id = ++i) })

      // Enter any new modes at the parent's previous position.
      let nodeEnter = node.enter().append('g')
        .attr('class', d => `node ${d.data.class}`)
        .attr('transform', d => `translate(${source.y0},${source.x0})`)
        .on('click', this.clickModel)

      // Add Circle for the nodes
      nodeEnter.append('circle')
        .attr('class', 'node')
        .attr('r', 1e-6)

      // Add labels for the nodes
      nodeEnter.append('text')
        .attr('dy', '.35em')
        .attr('x', d => d.children || d._children ? -13 : -13)
        .attr('text-anchor', d => d.children || d._children ? 'end' : 'end')
        .text(d => d.data.name)

      // UPDATE
      let nodeUpdate = nodeEnter.merge(node)

      // Transition to the proper position for the node
      nodeUpdate.transition()
        .duration(this.duration)
        .attr('transform', d => `translate(${d.y},${d.x})`)

      // Update the node attributes and style
      nodeUpdate.select('circle.node')
        .attr('r', 8)
        .attr('cursor', 'pointer')
        .style('fill', d => d.data.name === this.currentModel ? '#58b99dab' : 'white')

      // Remove any exiting nodes
      let nodeExit = node.exit().transition()
        .duration(this.duration)
        .attr('transform', d => `translate(${source.y},${source.x})`)
        .remove()

      // On exit reduce the node circles size to 0
      nodeExit.select('circle')
        .attr('r', 1e-6)

      // On exit reduce the opacity of text labels
      nodeExit.select('text')
        .style('fill-opacity', 1e-6)

      // ****************** links section ***************************

      // Update the links...
      let link = this.graph.selectAll('path.link')
        .data(links, d => d.id)

      // Enter any new links at the parent's previous position.
      let linkEnter = link.enter().insert('path', 'g')
        .attr('class', 'link')
        .attr('d', d => {
          let o = {
            x: source.x0,
            y: source.y0
          }
          return this.diagonal(o, o)
        })

      // UPDATE
      let linkUpdate = linkEnter.merge(link)

      // Transition back to the parent element position
      linkUpdate.transition()
        .duration(this.duration)
        .attr('d', d => this.diagonal(d, d.parent))

      // Remove any exiting links
      link.exit().transition()
        .duration(this.duration)
        .attr('d', d => {
          let o = {
            x: source.x,
            y: source.y
          }
          return this.diagonal(o, o)
        })
        .remove()

      // Store the old positions for transition.
      nodes.forEach(d => {
        d.x0 = d.x
        d.y0 = d.y
      })

      this.$emit('update-size')
    },
    diagonal (s, d) {
      const path2 = `
        M ${s.y} ${s.x}
        C ${(s.y + d.y) / 2} ${s.x},
          ${(s.y + d.y) / 2} ${d.x},
          ${d.y} ${d.x}
      `
      return path2
    },
    click (d) {
      if (d.children) {
        d._children = d.children
        d.children = null
      } else {
        d.children = d._children
        d._children = null
      }
      this.update(d)
    },
    clickModel (d) {
      this.$store.dispatch(SHOW_PANEL, {
        bioModel: d.data.name
      })
    }
  }
}
</script>

<style lang="scss">
.individual-tree-div {
  overflow-x: scroll;
  margin: auto;

  .node {
    cursor: pointer;
  }
  .node {
    &.individual circle {
      fill: #37495b;
      stroke: #37495b;
      stroke-width: 4px;
      &.close {
        fill: #37495b;
      }
    }
    &.sample circle {
      fill: #58b99d;
      stroke: #58b99d;
      stroke-width: 4px;
      &.close {
        fill: #58b99d;
      }
    }
    &.experiment circle {
      fill: #e9c545;
      stroke: #e9c545;
      stroke-width: 4px;
      &.close {
        fill: #e9c545;
      }
    }
  }
  .node text {
    font: 12px sans-serif;
  }
  .link {
    fill: none;
    stroke: #ccc;
    stroke-width: 2px;
  }
}

.individual-tree-panel .panel-content {
  height: auto;
}

.bio-model-tree .container {
  background: white;
  padding: 12px 12px 0px 0px !important;
  border: 1px solid rgba(0,0,0,0.12);
}
</style>
