import 'whatwg-fetch';
import { forceCenter, forceLink, forceManyBody, forceSimulation } from 'd3-force';
import { select, selectAll } from 'd3-selection';
import PropTypes from 'prop-types';
import React from 'react';
import _get from 'lodash/get';
import _isEmpty from 'lodash/isEmpty';
import _map from 'lodash/map';
import _uniq from 'lodash/uniq';
import { drag } from 'd3-drag';
import { format } from 'd3-format';
import { metricsPropType } from './util/MetricUtils.jsx';
import { withContext } from './util/AppContext.jsx';
import withREST from './util/withREST.jsx';

// create a Object with only the subset of functions/submodules/plugins that we need
const d3 = {
  drag,
  forceCenter,
  forceLink,
  forceManyBody,
  forceSimulation,
  select,
  selectAll,
  format,
};

const defaultSvgWidth = 524;
const defaultSvgHeight = 325;
const defaultNodeRadius = 15;
const margin = { top: 0, right: 0, bottom: 10, left: 0 };

const simulation = d3.forceSimulation()
  .force('link',
    d3.forceLink()
      .id(d => d.id)
      .distance(140))
  .force('charge', d3.forceManyBody().strength(-20))
  .force('center', d3.forceCenter(defaultSvgWidth / 2, defaultSvgHeight / 2));

export class NetworkGraphBase extends React.Component {
  constructor(props) {
    super(props);

    // https://github.com/d3/d3-zoom/issues/32
    d3.getEvent = (() => require('d3-selection').event); // eslint-disable-line global-require
  }

  componentDidMount() {
    const container = document.getElementsByClassName('network-graph-container')[0];
    const width = !container ? defaultSvgWidth : container.getBoundingClientRect().width;

    this.svg = d3.select('.network-graph-container')
      .append('svg')
      .attr('class', 'network-graph')
      .attr('width', width)
      .attr('height', width)
      .append('g')
      .attr('transform', `translate(${margin.left},${margin.top})`);
  }

  componentDidUpdate() {
    simulation.alpha(1).restart();
    this.drawGraph();
  }

  getGraphData() {
    const { data, deployments } = this.props;
    const links = [];
    const nodeList = [];

    _map(data, (resp, i) => {
      const rows = _get(resp, ['ok', 'statTables', 0, 'podGroup', 'rows']);
      const dst = deployments[i].name;
      _map(rows, row => {
        links.push({
          source: row.resource.name,
          target: dst,
        });
        nodeList.push(row.resource.name);
        nodeList.push(dst);
      });
    });

    const nodes = _map(_uniq(nodeList), n => ({ id: n }));
    return {
      links,
      nodes,
    };
  }

  drawGraph() {
    const graphData = this.getGraphData();

    // check if graph is present to prevent drawing of multiple graphs
    if (this.svg.select('circle')._groups[0][0]) {
      return;
    }
    this.drawGraphComponents(graphData.links, graphData.nodes);
  }

  drawGraphComponents(links, nodes) {
    if (_isEmpty(nodes)) {
      d3.select('.network-graph-container').select('svg').attr('height', 0);
      return;
    } else {
      d3.select('.network-graph-container').select('svg').attr('height', defaultSvgHeight);
    }

    this.svg.append('svg:defs').selectAll('marker')
      .data(links) // Different link/path types can be defined here
      .enter()
      .append('svg:marker') // This section adds in the arrows
      .attr('id', node => `${node.source}/${node.target}`)
      .attr('viewBox', '0 -5 10 10')
      .attr('refX', 24)
      .attr('refY', -0.25)
      .attr('markerWidth', 3)
      .attr('markerHeight', 3)
      .attr('fill', '#454242')
      .attr('orient', 'auto')
      .append('svg:path')
      .attr('d', 'M0,-5L10,0L0,5');

    // add the links and the arrows
    const path = this.svg.append('svg:g').selectAll('path')
      .data(links)
      .enter()
      .append('svg:path')
      .attr('stroke-width', 3)
      .attr('stroke', '#454242')
      .attr('marker-end', node => `url(#${node.source}/${node.target})`);

    const nodeElements = this.svg.append('g')
      .selectAll('circle')
      .data(nodes)
      .enter()
      .append('circle')
      .attr('r', defaultNodeRadius)
      .attr('fill', 'steelblue')
      .call(d3.drag()
        .on('start', this.dragstarted)
        .on('drag', this.dragged)
        .on('end', this.dragended));

    const textElements = this.svg.append('g')
      .selectAll('text')
      .data(nodes)
      .enter()
      .append('text')
      .text(node => node.id)
      .attr('font-size', 15)
      .attr('dx', 20)
      .attr('dy', 4);

    simulation.nodes(nodes).on('tick', () => {
      path
        .attr('d', node => `M${node.source.x} ${node.source.y} L ${node.target.x} ${node.target.y}`);

      nodeElements
        .attr('cx', node => node.x)
        .attr('cy', node => node.y);

      textElements
        .attr('x', node => node.x)
        .attr('y', node => node.y);
    });

    simulation.force('link')
      .links(links);
  }

  dragstarted = d => {
    if (!d3.getEvent().active) {
      simulation.alphaTarget(0.3).restart();
    }
    d.fx = d.x;
    d.fy = d.y;
  }

  dragged = d => {
    d.fx = d3.getEvent().x;
    d.fy = d3.getEvent().y;
  }

  dragended = d => {
    if (!d3.getEvent().active) {
      simulation.alphaTarget(0);
    }
    d.fx = null;
    d.fy = null;
  }

  render() {
    return (
      <div>
        <div className="network-graph-container" />
      </div>
    );
  }
}

NetworkGraphBase.defaultProps = {
  deployments: [],
};

NetworkGraphBase.propTypes = {
  data: PropTypes.arrayOf(metricsPropType.isRequired).isRequired,
  deployments: PropTypes.arrayOf(PropTypes.object),
};

export default withREST(
  withContext(NetworkGraphBase),
  ({ api, namespace, deployments }) => {
    return _map(deployments, d => {
      return api.fetchMetrics(`${api.urlsForResource('deployment', namespace)}&to_name=${d.name}`);
    });
  },
  {
    poll: false,
    resetProps: ['deployment'],
  },
);
