/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

<template>
  <div class="micro-topo-chart"></div>
</template>
<script lang="js">
import * as d3 from 'd3';
import d3tip from 'd3-tip';
/* tslint:disable */
const diagonal = d3.linkHorizontal()
  .x(function (d) { return d.x })
  .y(function (d) { return d.y });
const diagonalvertical = d3.linkVertical()
  .x(function (d) { return d.x })
  .y(function (d) { return d.y });

export default {
  props: {
    datas: {
      type: Object,
      default() {
        return {
          nodes: [],
          calls: [],
        };
      },
    },
  },
  data() {
    return {
      LOCAL: require('./assets/Local2.png'),
      CUBE: require('./assets/cube22.png'),
      CUBEERROR: require('./assets/cube21.png'),
      USER: require('./assets/USER.png'),
      UNKNOWN: require('./assets/UNKNOWN.png'),
      UNKNOWNCLOUD: require('./assets/UNKNOWN_CLOUD.png'),
      UNDEFINED: require('./assets/UNDEFINED.png'),
      KAFKA: require('./assets/KAFKALOGO.png'),
      KAFKACONSUMER: require('./assets/KAFKALOGO.png'),
      H2:require('./assets/H2.png'),
      REDIS:require('./assets/REDIS.png'),
      TOMCAT: require('./assets/TOMCAT.png'),
      HTTPCLIENT: require('./assets/HTTPCLIENT.png'),
      DUBBO: require('./assets/DUBBO.png'),
      MOTAN: require('./assets/MOTAN.png'),
      RESIN: require('./assets/RESIN.png'),
      OKHTTP: require('./assets/OKHTTP.png'),
      SPRINGMVC: require('./assets/SPRINGMVC.png'),
      STRUTS2: require('./assets/STRUTS2.png'),
      NUTZMVC: require('./assets/SPRINGMVC.png'),
      NUTZHTTP: require('./assets/HTTPCLIENT.png'),
      JETTY:require('./assets/JETTY.png'),
      JETTYSERVER: require('./assets/JETTYSERVER.png'),
      GRPC: require('./assets/GRPC.png'),
      ORACLE: require('./assets/ORACLE.png'),
      MYSQL: require('./assets/MYSQL.png'),
      MYSQLGROUP: require('./assets/MYSQL.png'),
      MSSQLSERVER: require('./assets/MYSQL.png'),
      MSSQLSERVERGROUP: require('./assets/MYSQL.png'),
      MONGODB: require('./assets/MONGODB.png'),
      MONGODBGROUP: require('./assets/MONGODB.png'),
      ACTIVEMQ: require('./assets/ACTIVEMQ.png'),
      ELASTICSEARCH: require('./assets/ELASTICSEARCH.png'),
      FEIGNDEFAULTHTTP: require('./assets/FEIGNDEFAULTHTTP.png'),
      HPROSE: require('./assets/HPROSE.png'),
      HPROSE: require('./assets/POSTGRESQL.png'),
      RESIN: require('./assets/RESIN.png'),
      RABBITMQ: require('./assets/RABBITMQ.png'),
      SOFARPC: require('./assets/SOFARPC.png'),
      ROCKETMQ: require('./assets/ROCKETMQ.png'),
      ROCKETMQCONSUMER: require('./assets/ROCKETMQ.png'),
      HTTP: require('./assets/HTTPCLIENT.png'),
      RESTEASY: require('./assets/RESTEASY.png'),
      SOLR: require('./assets/SOLR.png'),
      ZOOKEEPER: require('./assets/ZOOKEEPER.png'),
      width: 600,
      height: 600,
      force: '',
      svg: '',
      graph: '',
      link: '',
      node: '',
      zoom: '',
    };
  },
  beforeDestroy() {
    this.tip.hide({}, this);
    window.removeEventListener('resize', this.resize);
    d3.selectAll('.d3-tip-grey').remove();
    // this.$store.commit('skywalking/setCurrentNode', []);
  },
  mounted() {
    window.addEventListener('resize', this.resize);
    this.tip = d3tip()
      .attr('class', 'd3-tip-grey')
      .offset([-8, 0])
      .html(d => {
        return `
      <div class="mb-5"><span class="grey">${this.$t('cpm')}: </span>${d.cpm}</div>
      <div class="mb-5"><span class="grey">${this.$t('detectPoint')}: </span>${this.$store.state.rocketTopo.detectPoints.join(' | ')}</div>
      <div><span class="grey">${this.$t('latency')}: </span>${d.latency}</div>
      `});
    this.tipName = d3tip()
      .attr('class', 'd3-tip-grey')
      .offset([-8, 0])
      .html(d => {
        return `<div>${d.name}</div>`});
    this.height = this.$el.clientHeight;
    this.svg = d3
      .select(this.$el)
      .append('svg')
      .style('display','block')
      .attr('width', '100%')
      .attr('height', this.height);
  },
  watch: {
    'datas.nodes': 'draw',
  },
  methods: {
    draw() {
      const codeId = this.datas.nodes.map(i => i.id);
      for (let i = 0; i < this.datas.calls.length; i += 1) {
        const element = this.datas.calls[i];
        if(codeId.indexOf(element.target) === -1 ) {
          this.datas.calls[i].target = this.datas.calls[i].source;
        }
      }
      this.svg.select('.graph').remove();
      this.force = d3
        .forceSimulation(this.datas.nodes)
        .force('collide', d3.forceCollide().radius(() => 65))
        .force('yPos', d3.forceY().strength(1))
        .force('xPos', d3.forceX().strength(1))
        .force('charge', d3.forceManyBody().strength(-520))
        .force( 'link', d3.forceLink(this.datas.calls).id(d => d.id))
        .force('center', d3.forceCenter(window.innerWidth / 2 + 100, this.height / 2))
        .on('tick', this.tick)
        .stop();
      this.graph = this.svg.append('g').attr('class', 'graph');
      this.svg.call(this.getZoomBehavior(this.graph));
      this.graph.call(this.tip);
      this.graph.call(this.tipName);
      this.svg.on('click', (d, i) => {
        event.stopPropagation();
        event.preventDefault();
        this.$store.commit('rocketTopo/SET_NODE', {});
        this.$store.dispatch('rocketTopo/CLEAR_TOPO_INFO');
        that.tip.hide({}, this);
        this.toggleNode(this.node, d, false);
        this.toggleLine(this.line, d, false);
        this.toggleLine(this.lineNode, d, false);
      });
      this.defs = this.graph.append('defs');
      this.arrowMarker = this.defs
        .append('marker')
        .attr('id', 'arrow')
        .attr('markerUnits', 'strokeWidth')
        .attr('markerWidth', '12')
        .attr('markerHeight', '12')
        .attr('viewBox', '0 0 12 12')
        .attr('refX', '11')
        .attr('refY', '6')
        .attr('orient', 'auto');
      const arrow_path = 'M2,2 L10,6 L2,10 L3,6 L2,2';
      this.arrowMarker.append('path').attr('d', arrow_path).attr('fill', '#217EF2');
      this.gnode = this.graph.append('g').selectAll('.node');
      const that = this;
      this.node = this.gnode.data(this.datas.nodes)
        .enter()
        .append('g')
        .call(d3.drag()
          .on('start', this.dragstart)
          .on('drag', this.dragged)
          .on('end', function(d, i) {
           that.tipName.show(d, this);
          }))
        .on('mouseover', function(d, i) {
           that.tipName.show(d, this);
        })
        .on('mouseout', function(d, i) {
          that.tipName.hide(d, this);
        })
        .on('click', function(d, i) {
          event.stopPropagation();
          that.tip.hide({}, this);
          that.node.attr('class', '');
          d3.select(this).attr('class', 'node-active');
          const copyD = JSON.parse(JSON.stringify(d));
          delete copyD.x;
          delete copyD.y;
          delete copyD.vx;
          delete copyD.vy;
          delete copyD.fx;
          delete copyD.fy;
          delete copyD.index;
          that.$store.commit('rocketTopo/SET_NODE', copyD);
          that.toggleNode(that.node, d, true);
          that.toggleLine(that.line, d, true);
          that.toggleLine(that.lineNode, d, true);
        });
      this.node
        .append('image')
        .attr('width', 49)
        .attr('height', 49)
        .attr('x', 2)
        .attr('y', 10)
        .attr('style', 'cursor: move;')
        .attr('xlink:href',d => {
          const type = d.type;
          if( d.sla < 98 ) {
            return this.CUBEERROR;
          }
          return this.CUBE;
        });
      this.node
        .append('image')
        .attr('width',32)
        .attr('height', 32)
        .attr('x', 6)
        .attr('y', -10)
        .attr('style', 'opacity: 0.5;')
        .attr('xlink:href',this.LOCAL);
      this.node
        .append('image')
        .attr('width', 18)
        .attr('height', 18)
        .attr('x', 13)
        .attr('y', -7)
        .attr('xlink:href',d => {
          if( !d.type || d.type === 'N/A') {
            return this['UNDEFINED']
          }
          return this[d.type.toUpperCase().replace('-','')];
        });
      this.node
        .append('text')
        .attr('class', 'node-text')
        .attr('text-anchor', 'middle')
        .attr('x', 22)
        .attr('y', 70)
        .text(d => d.name.length > 12 ? `${d.name.substring(0,12)}...`: d.name)
      this.glink = this.graph.append('g').selectAll('.link');
      this.link = this.glink.data(this.datas.calls).enter();
      this.line = this.link.append('path').attr('class', 'link')
        .attr('stroke-dasharray', '13 7')
        .attr('stroke', d => d.cpm ? '#217EF25f' : '#6a6d7777');
      const handleSelectLine = function(d, i) {
        that.tip.hide({}, this);
      }
      this.lineNode = this.link.append('rect').attr('class', 'link-node cp')
        .attr('width', 6)
        .attr('height', 6)
        .attr('rx', 3)
        .attr('ry', 3)
        .attr('fill', d => d.cpm ? '#217EF299' : '#6a6d7799')
        .on('click', function(d, i) {
          that.$store.commit('rocketTopo/SET_MODE', d.detectPoints) 
          event.stopPropagation();
          that.tip.hide({}, this);
          that.tip.show(d, this);
          that.$store.dispatch(that.$store.state.rocketTopo.mode ? 'rocketTopo/GET_TOPO_SERVICE_INFO' : 'rocketTopo/GET_TOPO_CLIENT_INFO', {id:d.id,duration: that.$store.getters.durationTime});
          that.$store.commit('rocketTopo/SET_CALLBACK', function() {
            that.tip.hide({}, this);
            that.tip.show(d, this);
            that.$store.dispatch(that.$store.state.rocketTopo.mode ? 'rocketTopo/GET_TOPO_SERVICE_INFO' : 'rocketTopo/GET_TOPO_CLIENT_INFO', {id:d.id,duration: that.$store.getters.durationTime});
          })
        });
      d3.timeout(() => {
        for (
          let i = 0,
            n = Math.ceil(
              Math.log(this.force.alphaMin()) /
                Math.log(1 - this.force.alphaDecay())
            );
          i < n;
          i += 1
        ) {
          this.force.tick();
          this.tick();
        }
      });
    },
    isLinkNode(currNode, node) {
    if (currNode.id === node.id) {
        return true;
    }
    return this.datas.calls.filter(i => 
      (i.source.id === currNode.id || i.target.id === currNode.id) &&
      (i.source.id === node.id || i.target.id === node.id)
    ).length;
  },
    toggleNode(nodeCircle, currNode, isHover) {
    if (isHover) {
      nodeCircle.sort((a, b) => a.id === currNode.id ? 1 : -1);
      nodeCircle
          .style('opacity', .2)
          .filter(node => this.isLinkNode(currNode, node))
          .style('opacity', 1);
    } else {
        nodeCircle.style('opacity', 1);
    }
},
toggleLine(linkLine, currNode, isHover) {
  if (isHover) {
    linkLine
      .style('opacity', .05)
      .style('animation', 'none')
      .filter(link => this.isLinkLine(currNode, link))
      .style('opacity', 1)
      .style('animation', 'dash 1s linear infinite');
      // .classed('link-active', true);
    } else {
      linkLine
        .style('opacity', 1)
        .style('animation', 'dash 1s linear infinite');
        // .classed('link-active', false);
    }
  },
isLinkLine(node, link) {
    return link.source.id == node.id || link.target.id == node.id;
},
toggleLineText(lineText, currNode, isHover) {
  if (isHover) {
    lineText
      .style('fill-opacity', link => this.isLinkLine(currNode, link) ? 1.0 : 0.0);
      } else {
      lineText
      .style('fill-opacity', '1.0');
    }
  },
    toggleMarker(marker, currNode, isHover) {
      if (isHover) {
        marker.filter(link => this.isLinkLine(currNode, link))
          .style('transform', 'scale(1.5)');
      } else {
        marker
          .attr('refX', nodeConf.radius.Company)
          .style('transform', 'scale(1)');
      }
    },
    resize() {
      this.svg.attr('height', document.body.clientHeight - 50);
    },
    tick() {
      this.line
        .attr('d', d => `M${d.source.x} ${d.source.y} Q ${(d.source.x + d.target.x)/2} ${(d.target.y + d.source.y)/2 - 80} ${d.target.x} ${d.target.y}`);
      this.lineNode.attr('transform', d => `translate(${(d.source.x + d.target.x)/2 - 3},${(d.target.y + d.source.y)/2 - 43})`);
      // this.linkText.attr('transform',d =>`translate(${(d.source.x + d.target.x) / 2},${(d.source.y + d.target.y) / 2})`);
      this.node.attr('transform', d => `translate(${d.x -  22},${d.y - 22})`);
    },
    getZoomBehavior(g) {
      const that = this;
      return d3
        .zoom()
        .scaleExtent([0.3, 10])
        .on('zoom', () => {
          that.tip.hide({}, this);
          that.tipName.hide({}, this);
          g.attr(
            'transform',
            `translate(${d3.event.transform.x},${d3.event.transform.y})scale(${
              d3.event.transform.k
            })`
          );
        });
    },
    dragstart(d) {
      const that = this;
      that.tipName.hide({}, this);
      this.node._groups[0].forEach(d => {
        d.__data__.fx = d.__data__.x;
        d.__data__.fy = d.__data__.y;
      });
      if (!d3.event.active) {
        this.force.alphaTarget(0.01).restart();
      }
      d3.event.sourceEvent.stopPropagation();
    },
    dragged(d) {
      const that = this;
      that.tipName.hide({}, this);
      d.fx = d3.event.x;
      d.fy = d3.event.y;
    },
    dragended() {
      if (!d3.event.active) {
        this.force.alphaTarget(0);
      }
    },
  },
};
</script>
<style lang="scss">
.micro-topo-chart{
  height: 100%;
  width: 100%;
  .node-name {
    cursor: move;
    font-size:14px;
    fill: #ddd;
  }
  .link {
    stroke-linecap: round;
    stroke-width: 1.3px;
    fill: none;
    animation: dash 1s linear infinite;
  }
  @keyframes dash {
    from {
      stroke-dashoffset: 20;
    }
    to {
      stroke-dashoffset: 0;
    }
  }
  .node-text{
    font-family: "Lato","Source Han Sans CN", "Microsoft YaHei", sans-serif;
    fill: #ddd;
    font-size:11px;
    opacity: 0.8;
  }
  .link-text {
    font-family: "Lato","Source Han Sans CN", "Microsoft YaHei", sans-serif;
    fill: #ddd;
    font-size:11px;
    opacity: 0.8;
  }
  .node {
    cursor: move;
    fill: #333840;
    stroke-width: 0;
  }
  .link-node{
    stroke-width: 6px;
    stroke: rgba(255, 255, 255, 0);
  }
}
</style>
