import * as d3 from 'd3';
import { getDis, getTransform, getAngle } from './utils';
import './index.less';

const IconRelation = `<i class="anticon"><svg width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><use xlink:href="#icon-S_Photo_Relationship"></use></svg></i>`;
const IconRemove = `<i class="anticon"><svg width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><use xlink:href="#icon-S_Photo_SafeBox"></use></svg></i>`;

const config = { R: 34 };
function createForceFactory(id, options) {
  let { nodes, links, width, height, onLineClick, onNodeClick, onZoom, onRelation, onRemove, hasIconAction } = options;
  let svg = d3
    .select(`#${id}`)
    .append('svg')
    .attr('width', width)
    .attr('height', height)
    .attr('xmlns', 'http://www.w3.org/2000/svg')
    .call(
      d3
        .zoom()
        .scaleExtent([0.5, 1.5])
        .on('zoom', function () {
          onZoom && onZoom(d3.event.transform);
          g.attr('transform', d3.event.transform);
        })
    );
  let g = svg.append('g');
  let defs = svg.append('defs');
  let simulation = d3
    .forceSimulation()
    .force(
      'link',
      d3
        .forceLink()
        .id((d) => d.id)
        .distance(300)
        .strength(0.03)
    )
    .force('charge', d3.forceManyBody())
    .force('center', d3.forceCenter(width / 2, height / 2))
    .force('collide', d3.forceCollide(100).strength(0.01))
    .on('tick', ticked);

  update(nodes, links);

  function update(nodes, links) {
    simulation.stop();

    simulation.nodes(nodes);

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

    createArrowIcon(links);

    createAvatar(nodes);

    createLinks(links);

    createCircle(nodes);

    simulation.alphaTarget(1).alphaDecay(1);

    simulation.restart();
  }

  function destory() {
    simulation.stop();
    g.remove();
    defs.remove();
    svg.remove();
    simulation = null;
  }

  function createArrowIcon(links) {
    const arrows = defs.selectAll('.defs-arrow').data(links, (d) => d.linkId);
    arrows.exit().remove();

    arrows
      .enter()
      .append('marker')
      // .merge(arrows)
      .attr('class', 'defs-arrow')
      .attr('id', (d) => `marker${d.linkId}`)
      .attr('markerWidth', 10) //marker视窗的宽
      .attr('markerHeight', 10) //marker视窗的高
      .attr('refX', 6) //refX和refY，指的是图形元素和marker连接的位置坐标
      .attr('refY', 4)
      .attr('orient', 'auto') //orient="auto"设置箭头的方向为自动适应线条的方向
      .attr('markerUnits', 'userSpaceOnUse') //marker是否进行缩放 ,默认值是strokeWidth,会缩放
      .append('path')
      .attr('d', 'M 0 0 8 4 0 8Z') //箭头的路径 从 （0,0） 到 （8,4） 到（0,8）
      .attr('fill', (d) => `var(--force-${d.target.type})`);
  }

  function createAvatar(nodes) {
    const defaultPath = '/micro-apps/micro-dep-librarys/images/error/unknow.png';
    const avatars = defs.selectAll('.defs-avatar').data(nodes, (d) => d.id+id);
    avatars.exit().remove();
    avatars
      .enter()
      .append('pattern')
      // .merge(avatars)
      .attr('class', 'defs-avatar')
      .attr('id', (d) => `avatar${d.id+id}`)
      // 两个取值userSpaceOnUse  objectBoundingBox
      .attr('patternUnits', 'objectBoundingBox')
      // <pattern>，x、y值的改变决定图案的位置，宽度、高度默认为pattern图案占填充图形的百分比。
      .attr('x', '0')
      .attr('y', '0')
      .attr('width', '1')
      .attr('height', '1')
      .append('image')
      .attr('class', 'avatar-image')
      .attr('xlink:href', (d) => d.avatar || defaultPath)
      .attr('src', (d) => d.avatar || defaultPath)
      .attr('height', config.R * 2)
      .attr('width', config.R * 2)
      .attr('preserveAspectRatio', 'xMaxYMid slice');
  }

  function createLinks(links) {
    const lineLinks = g.selectAll('.g-links').data(links, (d) => {
      return d.linkId;
    });

    lineLinks.exit().remove();

    const linkGroup = lineLinks
      .enter()
      .append('g')
      // .merge(lineLinks)
      .attr('id', (d) => d.linkId)
      .attr('class', 'g-links');

    linkGroup
      .append('path')
      .attr('class', (d) => `link-path links-${d.linkId}`)
      .attr('fill', 'transparent')
      .style('marker-end', (d) => `url(#marker${d.linkId})`)
      .attr('stroke', (d) => `var(--force-${d.target.type})`)
      .attr('stroke-width', '1px');

    const lineRect = linkGroup
      .append('g')
      .attr('class', 'link-rect-g')
      .on('click', onLineClick)
      .on('mouseover', function (d) {
        d3.select(this).select('rect').attr('fill', `var(--force-${d.target.type})`);

        d3.select(this).select('text').attr('fill', '#fff');
      })
      .on('mouseout', function (d) {
        d3.select(this).select('rect').attr('fill', '#fff');

        d3.select(this).select('text').attr('fill', `var(--force-${d.target.type})`);
      });

    lineRect
      .append('rect')
      .attr('width', 40)
      .attr('height', 20)
      .attr('fill', '#fff')
      .attr('stroke-width', '1px')
      .attr('stroke', (d) => `var(--force-${d.target.type})`)
      .attr('class', (d) => `link-rect rect-${d.linkId}`)
      .style('cursor', 'pointer');

    lineRect
      .append('text')
      .text((d) => d.relation)
      .attr('text-anchor', 'middle') // <text>文本中轴对齐方式居中  start | middle | end
      .attr('class', (d) => `line-rect-text rect-text-${d.linkId}`)
      .style('font-size', 12)
      .style('cursor', 'pointer')
      .attr('fill', (d) => `var(--force-${d.target.type})`);
  }

  function createCircle(nodes) {
    const circle = g.selectAll('.node-circle-g').data(nodes, (d) => d.id);
    circle.exit().remove();
    const group = circle
      .enter()
      .append('g')
      .attr('class', 'node-circle-g')
      .style('cursor', 'pointer')
      .attr('x', width / 2)
      .attr('y', height / 2)
      .on('click', onNodeClick)
      .call(calldragevent());

    group
      .append('circle')
      .attr('r', config.R + 2)
      .attr('fill', 'transparent')
      .attr('stroke', (d) => `var(--force-${d.type})`);

    group
      .append('circle')
      .attr('r', config.R + 1)
      .attr('fill', (d) => `var(--force-${d.type})`)
      .attr('stroke', 'transparent');

    group
      .append('circle')
      .attr('r', config.R)
      .attr('fill', (d) => `url(#avatar${d.id+id})`)
      .attr('stroke', '#fff');

    group
      .append('text')
      .text((d) => d.name)
      .attr('width', 100)
      .attr('height', 30)
      .attr('text-anchor', 'middle')
      .attr('x', 0)
      .attr('y', config.R + 20)
      .style('font-size', 12)
      .attr('fill', (d) => `var(--force-${d.type})`);
    if (hasIconAction) {

      const mask = group
        .append('foreignObject')
        .attr('height', config.R * 2 + 60 + 2)
        .attr('width', config.R * 2 + 60 + 2)
        .attr('x', -config.R - 30 - 1)
        .attr('y', -config.R - 30 - 1)
        .append('xhtml:div')
        .attr('class', 'hover-mask-circle');

        mask.append('xhtml:span').attr('class', (d) => {
          return d.type ==="nonVehicle" ?'none' :'circle-relation'
        }).on('click', onRelation).attr('title','关系挖掘').html(IconRelation);

      mask.append('xhtml:span').attr('class',(d) => {
        return d.type ==="nonVehicle" ?'circle-remove single' :'circle-remove'
      } ).on('click', onRemove).attr('title', '移除').html(IconRemove);
    }
  }

  function calldragevent() {
    return d3.drag().on('start', started).on('drag', dragged).on('end', ended);
  }

  function started(d) {
    if (!d3.event.active) {
      simulation.alphaTarget(0.001).restart();
    }
    d.fx = d.x;
    d.fy = d.y;
  }
  function dragged(d) {
    d.fx = d3.event.x;
    d.fy = d3.event.y;
  }
  function ended(d) {
    if (!d3.event.active) {
      simulation.alphaTarget(0.001).restart();
    }
  }

  function ticked() {
    // 线的位置
    g.selectAll('.g-links').attr('transform', (d) => getTransform(d.source, d.target, getDis(d.source, d.target)));
    // 修改每条线link位置
    g.selectAll('.link-path').attr('d', tickedLinePath);

    //设置rect text位置
    g.selectAll('.link-rect-g').attr('transform', function (d) {
      const y = tickedLineRectY(d);
      const x = getDis(d.source, d.target) / 2;
      return `translate(${x},${y})`;
    });
    g.selectAll('.line-rect-text').attr('transform', function (d) {
      const angle = getAngle(d.source, d.target, getDis(d.source, d.target));
      const text = g.select(`.rect-text-${d.linkId}`);
      if (angle > 90 || angle < -90) {
        text.style('transform-origin', '0 10px');
      } else {
        text.style('transform-origin', 'unset');
      }
      return `rotate(${angle > 90 || angle < -90 ? 180 : 0})`;
    });
    //修改rect大小
    g.selectAll('.link-rect').attr('width', function (d) {
      let w = 40;
      const text = document.querySelector(`#${id}`).querySelector(`.rect-text-${d.linkId}`);
      if (text) {
        w = text.getBBox().width + 12;
      }
      d3.select(this).attr('x', -w / 2);
      return w;
    });

    //修正文字位置
    g.selectAll('.line-rect-text').attr('y', 14);

    //圆点的位置
    g.selectAll('.node-circle-g').attr('transform', (d) => `translate(${d.x},${d.y})`);
  }

  function tickedLinePath(d) {
    const dis = getDis(d.source, d.target) - config.R - 2;
    if (d.sameTotal === 1 || (d.sameMiddleLink && d.sameUneven)) {
      return `M ${config.R + 1},0 L ${dis},0`;
    }
    let y;
    let distance = d.sameTotal === 2 ? 10 : 20;
    if (d.sameUneven) {
      y = Math.ceil(d.sameIndex / 2) * distance;
      if (d.source.y > d.target.y) {
        y = y * -1;
      }
    } else {
      y = (d.sameIndex / 2) * distance;
      if (d.source.y < d.target.y) {
        y = y * -1;
      }
    }
    return `M ${config.R + 1 - (d.sameIndex / 2) * 2},${y} L ${dis + (d.sameIndex / 2) * 1},${y}`;
  }

  function tickedLineRectY(d) {
    if (d.sameTotal === 1 || (d.sameMiddleLink && d.sameUneven)) {
      return -10;
    }
    let y;
    let distance = d.sameTotal === 2 ? 10 : 20;
    if (d.sameUneven) {
      y = Math.ceil(d.sameIndex / 2) * distance;
      if (d.source.y > d.target.y) {
        y = y * -1;
      }
    } else {
      y = (d.sameIndex / 2) * distance;
      if (d.source.y < d.target.y) {
        y = y * -1;
      }
    }
    return y - 10;
  }

  return { update, destory };
}

export default createForceFactory;
