/**
 * Created by geweimin on 17/4/1.
 */
import React from 'react';
/* eslint-disable */
import { autobind } from 'core-decorators';
import ActivityImpl from './activityImpl';
import styles from '../../styles/global/process.less';
import { Color } from './processAttr';
import LineBreakMeasurer from './lineBreakMeasurer';
import arrow from '../../../asset/arrow.svg';
import user_normal from '../../../asset/user_normal.png';
import user_active from '../../../asset/user_active.png';
import massage from '../../../asset/massage.svg';
import Polyline from './Polyline';
import { isArray } from '../../utils/StringUtils';
import { ExtendButton } from '../../../components';
import TitleBar from '../info/titleBar';

const START_EVENT_COLOR = Raphael.getRGB("rgb(251, 251, 251)");
const START_EVENT_STROKE_COLOR = Color.black;
const NORMAL_STROKE = 1;
const NORMAL_FONT = {font: "10px Arial", opacity: 1, fill: '#93C7EB'};
const CATCHING_EVENT_COLOR = Color.black;
const NONE_END_EVENT_STROKE_COLOR = Color.Firebrick4;
const NONE_END_EVENT_COLOR = Color.Firebrick4;
const TASK_CORNER_ROUND = 10;
const TASK_STROKE_COLOR = '#2B97E0';
const TASK_FONT = {font: "11px Arial", opacity: 1, fill: '#2B97E0'};
const LABEL_FONT = {font: "11px Arial", "font-style": "italic", opacity: 1, "fill": LABEL_COLOR};
const LABEL_COLOR = Color.get(72, 106, 150);
const TEXT_PADDING = 3;
const ICON_SIZE = 16;
const ICON_PADDING = 4;
const MARKER_WIDTH = 12;
const TASK_COLOR = Color.white;
const MULTILINE_VERTICAL_ALIGN_MIDDLE = "middle";
const MULTILINE_HORIZONTAL_ALIGN_MIDDLE = "middle";
const MULTILINE_HORIZONTAL_ALIGN_RIGHT = "end";
const GATEWAY_TYPE_STROKE = 3.2;
const ARROW_WIDTH = 4;
const THICK_TASK_BORDER_STROKE = 2.5;
const CONNECTION_TYPE = {
  SEQUENCE_FLOW: "sequence_flow",
  MESSAGE_FLOW: "message_flow",
  ASSOCIATION: "association"
};
const imgArr = {
  userTask: {
    normal: user_normal,
    highLight: user_active,
    active: user_active
  }
};
const EventUtil = {
  addHandler: (element, type, handler) => {
    if (element.addEventListener) {
      element.addEventListener(type, handler, false);
    } else if (element.attachEvent) {
      element.attachEvent('on' + type, handler);
    } else {
      element['on' + type] = handler;
    }
  },
  getEvent: (event) => {
    return event ? event : window.event;
  },
  stopPropagation: (event) => {
    event = event || window.event;
    if (event.stopPropagation) {
      event.stopPropagation();
    } else {
      event.cancelBubble = true;
    }
  }
};
const RaphaelOriginal = Raphael;

export default class Process extends React.Component {
  constructor(props) {
    super(props);
    const { dataSource, query } = this.props;
    const svgSize = this.getSvgSize(dataSource);
    this.activityDrawInstructions = {};
    this.timeStamp = 0;
    this._initActivityDrawInstructions();
    this.zoomNum = 0;
    this.state = {
      svgSize: svgSize,
      highlightActivity: dataSource.highLightedActivities[0],
      taskList: null,
      processInstanceId: 225094,
      processDefinition: dataSource.processDefinition.id,
      taskListActionLink: '/Api/BPM/process-instance/' + query.processInstanceId + '/taskHistory',
      radius: 5,
      closeBtnClassName: 'burger'
    };
  }

  @autobind
  _initActivityDrawInstructions() {

    // start event
    this.activityDrawInstructions["startEvent"] = activityImpl => {
      this.g.setStart();
      let x = activityImpl.getX();
      let y = activityImpl.getY();
      let width = activityImpl.getWidth();
      let height = activityImpl.getHeight();
      const originalPaint = this._getPaint();
      if (typeof(START_EVENT_STROKE_COLOR) !== "undefined") {
        this._setPaint(START_EVENT_STROKE_COLOR);
      }

      width -= this._getStroke() / 2;
      height -= this._getStroke() / 2;

      x = x + width / 2;
      y = y + height / 2;

      const circle = this.g.ellipse(x, y, width / 2, height / 2);
      //TODO: TEST
      circle.attr({
        "stroke-width": this.strokeWidth,
        "stroke": this.paint,
        "fill": START_EVENT_COLOR
      });

      circle.id = activityImpl.id;
      circle.data("contextObject", activityImpl);

      this._setPaint(originalPaint);
      const set = this.g.setFinish();
      // this.addHandlers(set, x, y, width, height, "event", activityImpl, circle);
    };

    // end event
    this.activityDrawInstructions["endEvent"] = activityImpl => {

      this.g.setStart();
      let x = activityImpl.getX();
      let y = activityImpl.getY();
      let width = activityImpl.getWidth();
      let height = activityImpl.getHeight();
      const originalPaint = this._getPaint();
      if (typeof(START_EVENT_STROKE_COLOR) !== "undefined") {
        this._setPaint(START_EVENT_STROKE_COLOR);
      }

      width -= this._getStroke() / 2;
      height -= this._getStroke() / 2;

      x = x + width / 2;
      y = y + height / 2;

      const circle = this.g.ellipse(x, y, width / 2, height / 2);
      //TODO: TEST
      circle.attr({
        "stroke-width": this.strokeWidth + 1,
        "stroke": this.paint,
        "fill": START_EVENT_COLOR
      });

      circle.id = activityImpl.id;
      circle.data("contextObject", activityImpl);

      this._setPaint(originalPaint);
      const set = this.g.setFinish();
      // this.addHandlers(set, x, y, width, height, "event", activityImpl, circle);
    };

    // user task
    this.activityDrawInstructions["userTask"] = activityImpl => {
      this.drawTask(activityImpl, imgArr.userTask.normal)
    };

    // script task
    this.activityDrawInstructions["scriptTask"] = activityImpl => {
      this.drawTask(activityImpl, imgArr.userTask.normal)
    };

    // service task
    this.activityDrawInstructions["serviceTask"] = activityImpl => {
      this.drawTask(activityImpl, imgArr.userTask.normal)
    };

    // exclusive gateway
    this.activityDrawInstructions["exclusiveGateway"] = activityImpl => {
      const x = activityImpl.getX();
      const y = activityImpl.getY();
      const width = activityImpl.getWidth();
      const height = activityImpl.getHeight();
      this.g.setStart();

      const shape = this.drawGateway(x, y, width, height, activityImpl);

      const quarterWidth = width / 4;
      const quarterHeight = height / 4;

      // X inside rhombus
      const originalStroke = this._getStroke();
      this._setStroke(GATEWAY_TYPE_STROKE);

      const iks = this.g.path(
        "M" + (x + quarterWidth + 3) + " " + (y + quarterHeight + 3) + "L" + (x + 3 * quarterWidth - 3) + " " + (y + 3 * quarterHeight - 3) +
        "M" + (x + quarterWidth + 3) + " " + (y + 3 * quarterHeight - 3) + "L" + (x + 3 * quarterWidth - 3) + " " + (y + quarterHeight + 3)
      );
      iks.attr({"stroke-width": this._getStroke(), "stroke": this._getPaint()});

      this._setStroke(originalStroke);

      const set = this.g.setFinish();
      // this.addHandlers(set, x, y, width, height, "gateway", shape);
    };

    // inclusive gateway
    this.activityDrawInstructions["inclusiveGateway"] = function(){
      const x = activityImpl.getX();
      const y = activityImpl.getY();
      const width = activityImpl.getWidth();
      const height = activityImpl.getHeight();
      this.g.setStart();

      // rhombus
      const shape = this.drawGateway(x, y, width, height, activityImpl);

      const diameter = width / 4;

      // circle inside rhombus
      const originalStroke = this._getStroke();
      this._setStroke(GATEWAY_TYPE_STROKE);
      const circle = this.g.ellipse(width / 2 + x, height / 2 + y, diameter, diameter);
      circle.attr({"stroke-width": this._getStroke(), "stroke": this._getPaint()});

      this._setStroke(originalStroke);

      const set = this.g.setFinish();
      // this.addHandlers(set, x, y, width, height, "gateway", shape);
    };

    // parallel gateway
    this.activityDrawInstructions["parallelGateway"] = function(){
      const x = activityImpl.getX();
      const y = activityImpl.getY();
      const width = activityImpl.getWidth();
      const height = activityImpl.getHeight();
      this.g.setStart();

      // rhombus
      const shape = this.drawGateway(x, y, width, height, activityImpl);

      // plus inside rhombus
      const originalStroke = this._getStroke();
      this._setStroke(GATEWAY_TYPE_STROKE);

      const plus = this.g.path(
        "M" + (x + 10) + " " + (y + height / 2) + "L" + (x + width - 10) + " " + (y + height / 2) +	// horizontal
        "M" + (x + width / 2) + " " + (y + height - 10) + "L" + (x + width / 2) + " " + (y + 10)	// vertical
      );
      plus.attr({"stroke-width": this._getStroke(), "stroke": this._getPaint()});

      this._setStroke(originalStroke);

      const set = this.g.setFinish();
      // this.addHandlers(set, x, y, width, height, "gateway", shape);
    };
  }

  @autobind
  drawGateway(x, y, width, height, activityImpl) {

    const rhombus = this.g.path("M" + x + " " + (y + (height / 2)) +
      "L" + (x + (width / 2)) + " " + (y + height) +
      "L" + (x + width) + " " + (y + (height / 2)) +
      "L" + (x + (width / 2)) + " " + y +
      "z"
    );

    // white shaddow
    // this.drawShaddow(rhombus);

    rhombus.attr("stroke-width", this.strokeWidth);
    rhombus.attr("stroke", Color.SlateGrey);
    rhombus.attr({fill: Color.white});

    rhombus.id = activityImpl.id;
    rhombus.data("contextObject", activityImpl);

    return rhombus;
  }

  @autobind
  drawTask(activityImpl, imgArr1) {
    const name = activityImpl.getProperty("name");
    let x = activityImpl.getX();
    let y = activityImpl.getY();
    let width = activityImpl.getWidth();
    let height = activityImpl.getHeight();
    this.g.setStart();
    const shape = this._drawTask(name, x, y, width, height, activityImpl);
    const img = this.g.image(imgArr1, x + ICON_PADDING, y + ICON_PADDING, ICON_SIZE, ICON_SIZE);
    img.id = activityImpl.id + 'Image';
    const set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, "task", activityImpl, shape);
  }

  @autobind
  _drawTask(name, x, y, width, height, activityImpl) {
    const originalPaint = this._getPaint();
    this._setPaint(TASK_COLOR);

    // anti smoothing
    if (this.strokeWidth % 2 === 1) {
      x = Math.round(x) + .5, y = Math.round(y) + .5;
    }

    // shape
    const shape = this.g.rect(x, y, width, height, TASK_CORNER_ROUND);
    const attr = {"stroke-width": this.strokeWidth, stroke: TASK_STROKE_COLOR, fill: this._getPaint()};
    shape.attr(attr);
    shape.glow({ width: 4, fill: false, color: '#2b97e0' });
    //shape.attr({fill: "90-"+this.getPaint()+"-" + Color.get(250, 250, 244)});

    shape.id = activityImpl.id;
    shape.data("contextObject", activityImpl);

    this._setPaint(originalPaint);

    if (name) {
      const fontAttr = TASK_FONT;

      // Include some padding
      const paddingX = 5;
      const paddingY = 5;
      const availableTextSpace = width - paddingX * 2;

      const boxWidth = width - (2 * TEXT_PADDING);
      const boxHeight = height - ICON_SIZE - ICON_PADDING - ICON_PADDING - MARKER_WIDTH - 2 - 2;
      const boxX = x + width / 2 - boxWidth / 2;
      const boxY = y + height / 2 - boxHeight / 2 + ICON_PADDING + ICON_PADDING - 2 - 2;

      this._drawTaskLabel(name, boxX, boxY, boxWidth, boxHeight);
    }
    return shape;
  }

  @autobind
  _drawTaskLabel(text, x, y, boxWidth, boxHeight) {
    const originalFont = this._getFont();
    this._setFont(TASK_FONT);

    let verticalAlign = MULTILINE_VERTICAL_ALIGN_MIDDLE;
    const horizontalAlign = MULTILINE_HORIZONTAL_ALIGN_MIDDLE;

    if (!text || text === "") return;

    // Autostretch boxHeight if boxHeight is 0
    if (boxHeight === 0) {
      verticalAlign = MULTILINE_VERTICAL_ALIGN_TOP;
    }

    //var TEXT_PADDING = 3;
    const width = boxWidth;
    let height = null;
    if (boxHeight) {
      height = boxHeight;
    }

    const layouts = [];

    //var font = {font: "11px Arial", opacity: 1, "fill": LABEL_COLOR};
    const font = this._getFont();
    const measurer = new LineBreakMeasurer(this.g, x, y, text, font);
    const lineHeight = measurer.rafaelTextObject.getBBox().height;
    //console.log("text: ", text.replace(/\n/g, "?"));
    let availableLinesCount = null;
    if (height) {
      availableLinesCount = parseInt(height / lineHeight);
    }

    let i = 1;
    while (measurer.getPosition() < measurer.text.getEndIndex()) {
      const layout = measurer.nextLayout(width);
      //console.log("LAYOUT: " + layout + ", getPosition: " + measurer.getPosition());

      if (layout !== null) {
        // TODO: and check if measurer has next layout. If no then don't draw  dots
        if (!availableLinesCount || i < availableLinesCount) {
          layouts.push(layout);
        } else {
          layouts.push(this._fitTextToWidth(layout + "...", boxWidth));
          break;
        }
      }
      i++;
    }
    //console.log(layouts);

    measurer.rafaelTextObject.attr({"text": layouts.join("\n")});

    if (horizontalAlign)
      measurer.rafaelTextObject.attr({"text-anchor": horizontalAlign}); // end, middle, start

    const bb = measurer.rafaelTextObject.getBBox();
    // TODO: there is somethin wrong with wertical align. May be: measurer.rafaelTextObject.attr({"y": y + height/2 - bb.height/2})
    measurer.rafaelTextObject.attr({"y": y + bb.height / 2});
    //var bb = measurer.rafaelTextObject.getBBox();

    if (measurer.rafaelTextObject.attr("text-anchor") === MULTILINE_HORIZONTAL_ALIGN_MIDDLE)
      measurer.rafaelTextObject.attr("x", x + boxWidth / 2);
    else if (measurer.rafaelTextObject.attr("text-anchor") === MULTILINE_HORIZONTAL_ALIGN_RIGHT)
      measurer.rafaelTextObject.attr("x", x + boxWidth);

    const boxStyle = {stroke: Color.LightSteelBlue2, "stroke-width": 1.0, "stroke-dasharray": "- "};
    //var box = this.g.rect(x+.5, y + .5, width, height).attr(boxStyle);
    const textAreaCX = x + boxWidth / 2;
    let height1 = boxHeight;
    if (!height1) height1 = bb.height;
    const textAreaCY = y + height1 / 2;
    const dotLeftTop = this.g.ellipse(x, y, 3, 3).attr({
      "stroke-width": 0,
      fill: Color.LightSteelBlue,
      stroke: "none"
    }).hide();
    const dotCenter = this.g.ellipse(textAreaCX, textAreaCY, 3, 3).attr({
      fill: Color.LightSteelBlue2,
      stroke: "none"
    }).hide();

    /*
     // real bbox
     var bb = measurer.rafaelTextObject.getBBox();
     var rect = paper.rect(bb.x+.5, bb.y + .5, bb.width, bb.height).attr({"stroke-width": 1});
     */
    const rect = this.g.rect(x, y, boxWidth, height).attr({"stroke-width": 1}).attr(boxStyle).hide();
    const debugSet = this.g.set();
    debugSet.push(dotLeftTop, dotCenter, rect);

    this._setFont(originalFont);
  }

  @autobind
  _fitTextToWidth(original, width) {
    let text = original;

    // TODO: move attr on parameters
    const attr = {font: "11px Arial", opacity: 0};

    // remove length for "..."
    const dots = this.g.text(0, 0, "...").attr(attr).hide();
    const dotsBB = dots.getBBox();

    const maxWidth = width - dotsBB.width;

    const textElement = this.g.text(0, 0, text).attr(attr).hide();
    let bb = textElement.getBBox();

    // it's a little bit incorrect with "..."
    while (bb.width > maxWidth && text.length > 0) {
      text = text.substring(0, text.length - 1);
      textElement.attr({"text": text});
      bb = textElement.getBBox();
    }

    textElement.remove();

    if (text !== original) {
      text = text + "...";
    }

    return text;
  }

  @autobind
  addHandlers(set, x, y, width, height, type, contextObject, shape) {

    const cx = x + width / 2, cy = y + height / 2;
    let border = null;
    let overlay = null;
    if (type === "event") {
      border = this.g.ellipse(cx, cy, width / 2 + 4, height / 2 + 4);
      overlay = this.g.ellipse(cx, cy, width / 2, height / 2);
    } else if (type === "gateway") {
      // rhombus
      border = this.g.path("M" + (x - 4) + " " + (y + (height / 2)) +
        "L" + (x + (width / 2)) + " " + (y + height + 4) +
        "L" + (x + width + 4) + " " + (y + (height / 2)) +
        "L" + (x + (width / 2)) + " " + (y - 4) +
        "z");
      overlay = this.g.path("M" + x + " " + (y + (height / 2)) +
        "L" + (x + (width / 2)) + " " + (y + height) +
        "L" + (x + width) + " " + (y + (height / 2)) +
        "L" + (x + (width / 2)) + " " + y +
        "z");
    } else if (type === "task") {
      border = this.g.rect(x - 4, y - 4, width + 9, height + 9, TASK_CORNER_ROUND + 4);
      overlay = this.g.rect(x, y, width, height, TASK_CORNER_ROUND);
    }

    border.attr({stroke: RaphaelOriginal.getRGB("rgb(132, 112, 255)"), "stroke-width": 4, opacity: 0.0});
    border.id = contextObject.id + "_border";

    set.push(border);
    overlay[0].id = contextObject.id;
    overlay.attr({stroke: Color.Orange, "stroke-width": 3, fill: RaphaelOriginal.getRGB("rgb(0, 0, 0)"), opacity: 0.0, cursor: "hand"});
    overlay.data("set", set);
    overlay.id = contextObject.id;
    overlay.data("contextObject", contextObject);

    const that = this;

    if (type === "task") {
      overlay.click(function (event) {
        //TODO: click event
        console.log("click event");
        that.taskOnClick(shape);
      });
    }

    overlay.hover((event) => {
      const clientRect = overlay.node.getBoundingClientRect();
      let toolTips = document.querySelector('#toolTips');
      let arrow = document.querySelector('#tipsArrow');
      let tipContent = document.querySelector('#tipContent');
      if (!toolTips) {
        toolTips = document.createElement('div');
        toolTips.className = "toolTips";
        toolTips.id = 'toolTips';
        arrow = document.createElement('span');
        arrow.className = "tipsArrow";
        arrow.id = 'tipsArrow';
        toolTips.appendChild(arrow);
        tipContent = document.createElement('div');
        tipContent.className = "tipContent";
        tipContent.id = 'tipContent';
        toolTips.appendChild(tipContent);
      } else {
        toolTips.style.visibility = 'visible';
      }
      toolTips.style.top = clientRect.top + clientRect.height + 5 + 'px';
      toolTips.style.left = clientRect.left + 'px';
      document.getElementsByTagName('body')[0].append(toolTips);

      const vars = {};
      Object.keys(contextObject.properties).forEach(key => {
        const value = contextObject.properties[key];
        // if(key === "name") {
        //   Object.assign(vars, {"节点名": value});
        // } else if(key === "type") {
        //   Object.assign(vars, {"任务类型": value});
        // } else if(key === 'documentation') {
        //   Object.assign(vars, {"任务说明": value});
        // } else if(key === "isInterrupting") {
        //   Object.assign(vars, {"是否中断": value ? "是" : "否"});
        // } else
        if (key === "assignee") {
          Object.assign(vars, {"执行人：": value});
        } else {
          Object.assign(vars, {"执行人：": '未执行'});
        }
        // else if (key === "candidateGroups") {
        //   let valueRes = '(';
        //   value.forEach(function(item) {
        //     let valueTemp = '';
        //     if (item.indexOf('.') >= 0) {
        //       item.split('.').reduce(function(prev, item1) {
        //         return prev += item1 + ',';
        //       }, valueTemp);
        //       valueTemp = valueTemp.substr(0, valueTemp.length - 1);
        //     } else {
        //       valueTemp += item;
        //     }
        //     valueRes += valueTemp + '),(';
        //   });
        //   Object.assign(vars, {"可指派执行人": valueRes.substr(0, valueRes.length - 2)});
        // }
      });
      let tipContentChild = "<table cellSpacing=0 cellPadding=0 class='need-border'>";
      Object.keys(vars).forEach(varKey => {
        const varValue = vars[varKey];
        if (varValue) {
          tipContentChild += "<tr><td style='white-space: nowrap;text-align: right; padding-right: 5px; color: #011000;' valign='top'>" + varKey + "</td><td style='white-space: nowrap;'>" + varValue + "<td/></tr>";
        }
      });
      tipContentChild += "</table>";
      tipContent.innerHTML = tipContentChild;
    }, event => {
      document.getElementById('toolTips').style.visibility = 'hidden';
    });

    /*$('#' + contextObject.id).qtip({
     content: function() {
     var vars = {};
     $.each(contextObject.properties, function(key, value) {
     if(key == "name") {
     Object.assign(vars, {"节点名": value});
     } else if(key == "type") {
     Object.assign(vars, {"任务类型": value});
     } else if(key == 'documentation') {
     Object.assign(vars, {"任务说明": value});
     } else if(key == "isInterrupting") {
     Object.assign(vars, {"是否中断": value ? "是" : "否"});
     } else if (key == "assignee") {
     Object.assign(vars, {"执行人": value});
     } else if (key == "candidateGroups") {
     var valueRes = '(';
     value.forEach(function(item) {
     var valueTemp = '';
     if (item.indexOf('.') >= 0) {
     item.split('.').reduce(function(prev, item1) {
     return prev += item1 + ',';
     }, valueTemp);
     valueTemp = valueTemp.substr(0, valueTemp.length - 1);
     } else {
     valueTemp += item;
     }
     valueRes += valueTemp + '),(';
     });
     Object.assign(vars, {"可指派执行人": valueRes.substr(0, valueRes.length - 2)});
     }
     });
     var tipContent = "<table cellSpacing=0 cellPadding=0 class='need-border'>";
     $.each(vars, function(varKey, varValue) {
     if (varValue) {
     tipContent += "<tr><td class='label' valign='top'>" + varKey + "</td><td style='white-space: nowrap;'>" + varValue + "<td/></tr>";
     }
     });
     tipContent += "</table>";
     return tipContent;
     },
     position: {
     at: 'bottom left',
     adjust: {
     x: 3
     }
     },
     style: {
     classes: 'ui-tooltip-light ui-tooltip-shadow ui-tooltip-rounded'
     }
     });*/
  }

  @autobind
  _drawActivity(activity) {
    const type = activity.getProperty("type");
    const drawInstruction = this.activityDrawInstructions[type];
    if (drawInstruction) {
      drawInstruction(activity);
    } else {
      console.error("no drawInstruction for " + type + ": ", activity);
    }
  }

  componentDidMount() {
    document.getElementsByClassName('wrapper-panel')[0].style.height = '100%';
    const dataSource = this.props.dataSource;
    const { svgSize } = this.state;
    document.getElementById("diagramHolder").style.minWidth = svgSize.maxX + 'px';
    this.g = Raphael(document.getElementsByClassName("diagram")[0], svgSize.maxX, svgSize.maxY);
    this.g.clear();
    this._setPaint(Color.black);
    this._setStroke(NORMAL_STROKE);
    this._setFont(NORMAL_FONT);
    const self = this;
    this.ninjaPaper = (function (local_raphael) {
      return local_raphael(1, 1, 1, 1, self.state.processDefinition);
    })(Raphael.ninja());

    const laneSets = dataSource && dataSource.laneSets ? dataSource.laneSets : [];
    const activities = dataSource && dataSource.activities ? dataSource.activities : [];
    const sequenceFlows = dataSource && dataSource.sequenceFlows ? dataSource.sequenceFlows : [];

    let cnt = 0;
    if (laneSets) {
      laneSets.forEach(item => cnt += item.lanes.length);
    }

    if(activities) {
      cnt += activities.length;
    }
    if (sequenceFlows) {
      cnt += sequenceFlows.length;
    }
    let step = (cnt > 0) ? 100 / cnt : 0;
    let progress = 0;

    laneSets.forEach(item => {
      item.forEach(item1 => {
        // processDiagramCanvas.drawPoolOrLane(lane.x, lane.y, lane.width, lane.height, lane.name);
      });
    });

    activities.forEach(item => {
      const activity = new ActivityImpl(item);
      this._drawActivity(activity);
    });

    sequenceFlows.forEach(flow => {
      const waypoints = [];
      flow.xPointArray.forEach((item, index) => waypoints.push({x: item, y: flow.yPointArray[index]}));
      const isDefault = flow.isDefault;
      const isConditional = flow.isConditional;
      const isHighLighted = flow.isHighLighted;

      // TODO: add source and destination for sequence flows in REST
      // parse for test
      const f = flow.flow;
      const matches = f.match(/\((.*)\)--.*-->\((.*)\)/);
      let sourceActivityId, destinationActivityId;
      if (matches !== null) {
        sourceActivityId = matches[1];
        destinationActivityId = matches[2];
      }
      flow.sourceActivityId = sourceActivityId;
      flow.destinationActivityId = destinationActivityId;
      const withArrowHead = true;

      this._drawFlow(waypoints, isConditional, isDefault, isHighLighted, withArrowHead, CONNECTION_TYPE.SEQUENCE_FLOW, flow);
    });
    if (!this.props.dataSource.processFinish) {
      const shape = this.highLightActivity(this.state.highlightActivity);
      this.taskOnClick(shape);
    } else {
      this.highLightActivity(this.state.highlightActivity, true);
    }
    const diagramHolder = document.getElementById("diagramHolder");

    if (window.navigator.userAgent.indexOf('Firefox') !== -1) {
      EventUtil.addHandler(diagramHolder, 'DOMMouseScroll', this.handleMouseWheel);
    } else {
      EventUtil.addHandler(diagramHolder, 'mousewheel', this.handleMouseWheel);
    }

    const mask = document.querySelector('.first-loading-mask');
    if (mask) {
      mask.style.display = 'none';
    }

  }

  @autobind
  handleMouseWheel(event) {
    EventUtil.stopPropagation(event);
    event = EventUtil.getEvent(event);
    const value = event.wheelDelta || -event.detail;
    const delta = Math.max(-1, Math.min(1, value));
    if (delta === 1) {
      if (event.timeStamp - this.timeStamp > 600) {
        this.timeStamp = event.timeStamp;
        this.zoomClick("+");
      }
    } else if (delta === -1) {
      if (event.timeStamp - this.timeStamp > 600) {
        this.timeStamp = event.timeStamp;
        this.zoomClick("-");
      }
    }
    return false;
    // console.log('delta:' + delta + ' event.timeStamp:' + event.timeStamp + ' event.wheelDelta:' + event.wheelDelta);
  }

  @autobind
  highLightActivity(activityId, isFinish) {
    const shape = this.g.getById(activityId);
    if (!shape) {
      console.error("Activity " + activityId + " not found");
      return;
    }

    const contextObject = shape.data("contextObject");
    if (contextObject && !isFinish) {
      const img = this.g.getById(activityId + 'Image');
      if (img) {
        img.attr('src', imgArr[contextObject.properties.type].highLight);
        console.log("--> highLightActivity: [" + contextObject.getProperty("type") + "], activityId: " + contextObject.getId());
      }
    } else
      console.log("--> highLightActivity: ", shape, shape.data("contextObject"));
    // shape.attr("stroke", HIGHLIGHT_COLOR);
    if (!isFinish) {
      shape.attr("stroke-width", 0);
      shape.attr("fill", '#FFA94B');
      shape.next.attr('fill', '#FFFFFF');
      shape.node.id = activityId;
    } else {
      shape.attr("stroke-width", 3);
      shape.attr("stroke", NONE_END_EVENT_STROKE_COLOR);
    }
    return shape;
  }

  @autobind
  drawActiveActivity(activityId) {
    const shape = this.g.getById(activityId);
    console.log(shape);
    if (!shape) {
      console.error("Activity " + activityId + " not found");
      return;
    }

    const contextObject = shape.data("contextObject");
    if (contextObject)
      console.log("--> highLightActivity: [" + contextObject.getProperty("type") + "], activityId: " + contextObject.getId());
    else
      console.log("--> highLightActivity: ", shape, shape.data("contextObject"));
    if (activityId === this.state.highlightActivity) {
      shape.attr("stroke-width", THICK_TASK_BORDER_STROKE);
      shape.attr("stroke", "FFA94B");
    } else {
      shape.attr("stroke-width", 0);
      // shape.attr("stroke", ACTIVE_COLOR);
    }
    shape.attr("fill", '#2B97E0');
    shape.next.attr('fill', '#FFFFFF');
    const img = this.g.getById(activityId + 'Image');
    img.attr('src', imgArr[contextObject.properties.type].active);
  }

  @autobind
  normalActivity(activityId) {
    const shape = this.g.getById(activityId);
    if (!shape) {
      console.error("Activity " + activityId + " not found");
      return;
    }

    const contextObject = shape.data("contextObject");
    if (contextObject)
      console.log("--> highLightActivity: [" + contextObject.getProperty("type") + "], activityId: " + contextObject.getId());
    else
      console.log("--> highLightActivity: ", shape, shape.data("contextObject"));

    shape.attr("stroke-width", NORMAL_STROKE);
    shape.attr("stroke", TASK_STROKE_COLOR);
    shape.attr("fill", TASK_COLOR);
    shape.next.attr('fill', '#2B97E0');
    const img = this.g.getById(activityId + 'Image');
    img.attr('src', imgArr[contextObject.properties.type].normal);
  }

  @autobind
  taskOnClick(shape) {
    const overlay = shape;
    const set = overlay.data("set");
    const contextObject = overlay.data("contextObject");
    const activeActivity = this.activeActivity;
    if (contextObject.properties.type === 'startEvent' || contextObject.properties.type === 'endEvent') {
      return;
    }

    if (!activeActivity || activeActivity !== overlay.id) {
      this.drawActiveActivity(overlay.id);
      if (activeActivity) {
        if (activeActivity === this.state.highlightActivity) {
          this.highLightActivity(activeActivity);
        } else {
          this.normalActivity(activeActivity);
        }
      }
      this.activeActivity = overlay.id;
    }

    if (this.taskHistoryList && this.taskHistoryList.length > 0) {
      this._taskOnClick(contextObject, this.taskHistoryList);
    } else {
      this.props.exec(() => {
        return this.props.fetch(this.state.taskListActionLink, {}).then(data => {
          this._taskOnClick(contextObject, data);
          this.taskHistoryList = data;
        }).catch(err => {
          console.log('err:' + err);
        });
      });
    }
  }

  @autobind
  _taskOnClick(contextObject, taskHistoryList) {
    const id = contextObject.id;
    const taskList = [];
    taskHistoryList && isArray(taskHistoryList) && taskHistoryList.forEach((item, index) => {
      if (item.taskDefinitionKey === id) {
        taskList.push(Object.assign({ index }, item));
      }
    });

    taskList.map(item => {
      if (item.index < taskHistoryList.length - 1) {
        const afterIndex = item.index + 1;
        Object.assign(item, {
          afterAssigneeName: taskHistoryList[afterIndex].assigneeName,
          afterRoleName: taskHistoryList[afterIndex].roleName,
          afterDeptName: taskHistoryList[afterIndex].deptName,
        })
      } else {
        Object.assign(item, {
          afterAssigneeName: '',
          afterRoleName: '',
          afterDeptName: '',
        })
      }

      if (item.index > 0) {
        const frontIndex = item.index - 1;
        Object.assign(item, {
          frontAssigneeName: taskHistoryList[frontIndex].assigneeName,
          frontRoleName: taskHistoryList[frontIndex].roleName,
          frontDeptName: taskHistoryList[frontIndex].deptName,
        })
      } else {
        Object.assign(item, {
          frontAssigneeName: '',
          frontRoleName: '',
          frontDeptName: '',
        })
      }
      item.startTime = (new Date(item.startTime)).toLocaleString().substr(0, 9);
      item.endTime = item.endTime ? (new Date(item.endTime)).toLocaleString().substr(0, 9) : '';
      return item;
    });
    // taskList[0].frontAssigneeName = '粉红色的粉红色的很快就到合肥科技的首付款粉红色的粉红色的很快就到合肥科技的首付款粉红色的粉红色的很快就到合肥科技的首付款粉红色的粉红色的很快就到合肥科技的首付款粉红色的粉红色的很快就到合肥科技的首付款';
    // const tempTaskList = [];
    // tempTaskList.push(taskList[0]);
    // tempTaskList.push(taskList[0]);
    // tempTaskList.push(taskList[0]);
    // tempTaskList.push(taskList[0]);
    this.burgerClick(true);
    taskList.reverse();
    this.setState({
      taskList
    });
  }

  @autobind
  _drawFlow(waypoints, conditional, isDefault, highLighted, withArrowHead, connectionType, flow) {
    const originalPaint = this._getPaint();
    const originalStroke = this._getStroke();

    const SEQUENCEFLOW_COLOR = Color.black;
    const SEQUENCEFLOW_STROKE = 1.5;

    this._setPaint(SEQUENCEFLOW_COLOR);
    this._setStroke(SEQUENCEFLOW_STROKE);

// TODO: generate polylineId or do something!!
    const uuid = RaphaelOriginal.createUUID();

    const contextObject = flow;
    let newWaypoints = waypoints;
    if (contextObject) {
      newWaypoints = this._connectFlowToActivity(contextObject.sourceActivityId, contextObject.destinationActivityId, waypoints);

      if (!newWaypoints) {
        console.error("Error draw flow from '" + contextObject.sourceActivityId + "' to '" + contextObject.destinationActivityId + "' ");
        return;
      }
    }
    const polyline = new Polyline(uuid, newWaypoints, this._getStroke(), this.flowRebuildPath);
    // this.flowRebuildPath(polyline);
    //var polyline = new Polyline(waypoints, 3);

    polyline.element = this.g.path(polyline.path);
    polyline.element.attr("stroke-width", this._getStroke());
    polyline.element.attr("stroke", this._getPaint());

    if (contextObject) {
      polyline.element.id = contextObject.id;
      polyline.element.data("contextObject", contextObject);
    } else {
      polyline.element.id = uuid;
    }

    const last = polyline.getAnchorsCount() - 1;
    const x = polyline.getAnchor(last).x;
    const y = polyline.getAnchor(last).y;
    //var c = this.g.ellipse(x, y, 5, 5);

    const lastLineIndex = polyline.getLinesCount() - 1;
    const line = polyline.getLine(lastLineIndex);
    const firstLine = polyline.getLine(0);

    let arrowHead = null,
      circleTail = null,
      defaultSequenceFlowIndicator = null,
      conditionalSequenceFlowIndicator = null;

    if (connectionType === CONNECTION_TYPE.MESSAGE_FLOW) {
      circleTail = this._drawCircleTail(firstLine, connectionType);
    }
    if (withArrowHead)
      arrowHead = this._drawArrowHead(line, connectionType);

    //console.log("isDefault: ", isDefault, ", isDefaultConditionAvailable: ", polyline.isDefaultConditionAvailable);
    if (isDefault && polyline.isDefaultConditionAvailable) {
      //var angle = polyline.getLineAngle(0);
      //console.log("firstLine", firstLine);
      defaultSequenceFlowIndicator = this._drawDefaultSequenceFlowIndicator(firstLine);
    }

    //TODO: conditional的情况的处理
    /*if (conditional) {
     conditionalSequenceFlowIndicator = this._drawConditionalSequenceFlowIndicator(firstLine);
     }*/

    // draw flow name
    const flowName = contextObject.name;
    if (flowName) {
      const xPointArray = contextObject.xPointArray;
      const yPointArray = contextObject.yPointArray;
      let textX = xPointArray[0] < xPointArray[1] ? xPointArray[0] : xPointArray[1];
      let textY = yPointArray[0] < yPointArray[1] ? yPointArray[1] : yPointArray[0];
      // fix xy
      textX += 20;
      textY -= 10;
      this.g.text(textX, textY, flowName).attr(LABEL_FONT);
    }

    const st = this.g.set();
    st.push(polyline.element, arrowHead, circleTail, conditionalSequenceFlowIndicator);
    polyline.element.data("set", st);
    polyline.element.data("withArrowHead", withArrowHead);

    const polyCloneAttrNormal = {
      "stroke-width": this._getStroke() + 5,
      stroke: RaphaelOriginal.getRGB("rgb(132, 112, 255)"),
      opacity: 0.0,
      cursor: "hand"
    };
    const polyClone = st.clone().attr(polyCloneAttrNormal).hover(function () {
      //if (polyLine.data("isSelected")) return;
      polyClone.attr({opacity: 0.2});
    }, function () {
      //if (polyLine.data("isSelected")) return;
      polyClone.attr({opacity: 0.0});
    });
    polyClone.data("objectId", polyline.element.id);
    polyClone.data("parentId", uuid);

    if (!connectionType || connectionType === CONNECTION_TYPE.SEQUENCE_FLOW)
      polyline.element.attr("stroke-width", this._getStroke());
    else if (connectionType === CONNECTION_TYPE.MESSAGE_FLOW)
      polyline.element.attr({"stroke-dasharray": "--"});
    else if (connectionType === CONNECTION_TYPE.ASSOCIATION)
      polyline.element.attr({"stroke-dasharray": ". "});

    this._setPaint(originalPaint);
    this._setStroke(originalStroke);
  }

  @autobind
  _drawConditionalSequenceFlowIndicator(line) {
    const horizontal = (CONDITIONAL_INDICATOR_WIDTH * 0.7);
    const halfOfHorizontal = horizontal / 2;
    const halfOfVertical = CONDITIONAL_INDICATOR_WIDTH / 2;

    const uuid = null;
    const waypoints = [{x: 0, y: 0},
      {x: -halfOfHorizontal, y: halfOfVertical},
      {x: 0, y: CONDITIONAL_INDICATOR_WIDTH},
      {x: halfOfHorizontal, y: halfOfVertical}];
    /*
     var polyline = new Polyline(uuid, waypoints, this.getStroke());
     polyline.element = this.g.path(polyline.path);
     polyline.element.attr("stroke-width", this.getStroke());
     polyline.element.attr("stroke", this.getPaint());
     polyline.element.id = uuid;
     */
    const polygone = new Polygone(waypoints, this._getStroke());
    polygone.element = this.g.path(polygone.path);
    polygone.element.attr("fill", Color.white);

    polygone.transform("t" + line.x1 + "," + line.y1 + "");
    polygone.transform("...r" + RaphaelOriginal.deg(line.angle - Math.PI / 2) + " " + 0 + " " + 0);


    const cosAngle = Math.cos((line.angle));
    const sinAngle = Math.sin((line.angle));

    //polygone.element.attr("stroke-width", this.getStroke());
    //polygone.element.attr("stroke", this.getPaint());

    polygone.attr({"stroke-width": this._getStroke(), "stroke": this._getPaint()});

    return polygone.element;
  }

  @autobind
  _drawDefaultSequenceFlowIndicator(line) {
    //console.log("line: ", line);

    let len = 10;
    const c = len / 2, f = 8;
    const defaultIndicator = this.g.path("M" + (-c) + " " + 0 + "L" + (c) + " " + 0);
    defaultIndicator.attr("stroke-width", this._getStroke() + 0);
    defaultIndicator.attr("stroke", this._getPaint());


    const cosAngle = Math.cos((line.angle));
    const sinAngle = Math.sin((line.angle));

    const dx = f * cosAngle;
    const dy = f * sinAngle;

    const x1 = line.x1 + dx + 0 * c * cosAngle;
    const y1 = line.y1 + dy + 0 * c * sinAngle;

    defaultIndicator.transform("t" + (x1) + "," + (y1) + "");
    defaultIndicator.transform("...r" + RaphaelOriginal.deg(line.angle - 3 * Math.PI / 4) + " " + 0 + " " + 0);
    /*
     var c0 = this.g.ellipse(0, 0, 1, 1).attr({stroke: Color.Blue});
     c0.transform("t" + (line.x1) + "," + (line.y1) + "");
     var center = this.g.ellipse(0, 0, 1, 1).attr({stroke: Color.Red});
     center.transform("t" + (line.x1+dx) + "," + (line.y1+dy) + "");
     */

    return defaultIndicator;
  }

  @autobind
  _drawArrowHead(line, connectionType) {
    const doubleArrowWidth = 2 * ARROW_WIDTH;

    let arrowHead = null;
    if (connectionType === CONNECTION_TYPE.ASSOCIATION)
      arrowHead = this.g.path("M-" + (ARROW_WIDTH / 2 + .5) + " -" + doubleArrowWidth + "L 0 0 L" + (ARROW_WIDTH / 2 + .5) + " -" + doubleArrowWidth);
    else
      arrowHead = this.g.path("M0 0L-" + (ARROW_WIDTH / 2 + .5) + " -" + doubleArrowWidth + "L" + (ARROW_WIDTH / 2 + .5) + " -" + doubleArrowWidth + "z");

    //arrowHead.transform("t" + 0 + ",-" + this.getStroke() + "");

    // anti smoothing
    if (this.strokeWidth % 2 === 1)
      line.x2 += .5, line.y2 += .5;

    arrowHead.transform("t" + line.x2 + "," + line.y2 + "");
    arrowHead.transform("...r" + RaphaelOriginal.deg(line.angle - Math.PI / 2) + " " + 0 + " " + 0);

    if (!connectionType || connectionType === CONNECTION_TYPE.SEQUENCE_FLOW)
      arrowHead.attr("fill", this._getPaint());
    else if (connectionType === CONNECTION_TYPE.MESSAGE_FLOW)
      arrowHead.attr("fill", Color.white);

    arrowHead.attr("stroke-width", this._getStroke());
    arrowHead.attr("stroke", this._getPaint());

    return arrowHead;
  }

  @autobind
  _drawCircleTail(line, connectionType) {
    const diameter = ARROW_WIDTH / 2 * 1.5;

    // anti smoothing
    if (this.strokeWidth % 2 === 1)
      line.x1 += .5, line.y1 += .5;

    const circleTail = this.g.ellipse(line.x1, line.y1, diameter, diameter);
    circleTail.attr("fill", Color.white);
    circleTail.attr("stroke", this._getPaint());

    return circleTail;
  }

  @autobind
  _connectFlowToActivity(sourceActivityId, destinationActivityId, waypoints) {
    const sourceActivity = this.g.getById(sourceActivityId);
    const destinationActivity = this.g.getById(destinationActivityId);
    if (sourceActivity === null || destinationActivity === null) {
      if (sourceActivity === null)
        console.error("source activity[" + sourceActivityId + "] not found");
      else
        console.error("destination activity[" + destinationActivityId + "] not found");
      return null;
    }
    const bbSourceActivity = sourceActivity.getBBox();
    const bbDestinationActivity = destinationActivity.getBBox();

    const path = [];
    const newWaypoints = [];
    for (let i = 0; i < waypoints.length; i++) {
      let pathType = "";
      if (i === 0)
        pathType = "M";
      else
        pathType = "L";

      path.push([pathType, waypoints[i].x, waypoints[i].y]);
      newWaypoints.push({x: waypoints[i].x, y: waypoints[i].y});
    }

    const ninjaPathSourceActivity = this.ninjaPaper.path(sourceActivity.realPath);
    const ninjaPathDestinationActivity = this.ninjaPaper.path(destinationActivity.realPath);
    const ninjaBBSourceActivity = ninjaPathSourceActivity.getBBox();
    const ninjaBBDestinationActivity = ninjaPathDestinationActivity.getBBox();

    // set target of the flow to the center of the taskObject
    const newPath = path;
    const originalSource = {x: newPath[0][1], y: newPath[0][2]};
    const originalTarget = {x: newPath[newPath.length - 1][1], y: newPath[newPath.length - 1][2]};
    newPath[0][1] = ninjaBBSourceActivity.x + (ninjaBBSourceActivity.x2 - ninjaBBSourceActivity.x ) / 2;
    newPath[0][2] = ninjaBBSourceActivity.y + (ninjaBBSourceActivity.y2 - ninjaBBSourceActivity.y ) / 2;
    newPath[newPath.length - 1][1] = ninjaBBDestinationActivity.x + (ninjaBBDestinationActivity.x2 - ninjaBBDestinationActivity.x ) / 2;
    newPath[newPath.length - 1][2] = ninjaBBDestinationActivity.y + (ninjaBBDestinationActivity.y2 - ninjaBBDestinationActivity.y ) / 2;

    const ninjaPathFlowObject = this.ninjaPaper.path(newPath);
    const ninjaBBFlowObject = ninjaPathFlowObject.getBBox();

    const intersectionsSource = RaphaelOriginal.pathIntersection(ninjaPathSourceActivity.realPath, ninjaPathFlowObject.realPath);
    const intersectionsDestination = RaphaelOriginal.pathIntersection(ninjaPathDestinationActivity.realPath, ninjaPathFlowObject.realPath);
    const intersectionSource = intersectionsSource.pop();
    const intersectionDestination = intersectionsDestination.pop();
    let dotOriginal = null;
    let dot = null;
    if (intersectionSource !== undefined) {
      if (this.gebug) {
        const diameter = 5;
        dotOriginal = this.g.ellipse(originalSource.x, originalSource.y, diameter, diameter).attr({
          "fill": Color.white,
          "stroke": Color.Pink
        });
        dot = this.g.ellipse(intersectionSource.x, intersectionSource.y, diameter, diameter).attr({
          "fill": Color.white,
          "stroke": Color.Green
        });
      }

      newWaypoints[0].x = intersectionSource.x;
      newWaypoints[0].y = intersectionSource.y;
    }
    if (intersectionDestination !== undefined) {
      if (this.gebug) {
        const diameter = 5;
        dotOriginal = this.g.ellipse(originalTarget.x, originalTarget.y, diameter, diameter).attr({
          "fill": Color.white,
          "stroke": Color.Red
        });
        dot = this.g.ellipse(intersectionDestination.x, intersectionDestination.y, diameter, diameter).attr({
          "fill": Color.white,
          "stroke": Color.Blue
        });
      }

      newWaypoints[newWaypoints.length - 1].x = intersectionDestination.x;
      newWaypoints[newWaypoints.length - 1].y = intersectionDestination.y;
    }

    this.ninjaPaper.clear();
    return newWaypoints;
  }
  @autobind
  flowRebuildPath(polyline) {
    const path = [];

    for (let i = 0; i < polyline.getAnchorsCount(); i++) {
      const anchor = polyline.getAnchor(i);

      let pathType = "";
      if (i === 0)
        pathType = "M";
      else
        pathType = "L";

      let targetX = anchor.x, targetY = anchor.y;
      let dx0 = 0, dy0 = 0, ax = 0, ay = 0, dx1 = 0, dy1 = 0, bx = 0, by = 0, zx = 0, zy = 0;
      if (i > 0 && i < polyline.getAnchorsCount() - 1) {
        // get new x,y
        const cx = anchor.x, cy = anchor.y;

        // pivot point of prev line
        let AO = polyline.getLineLength(i - 1);
        if (AO < polyline.radius) {
          AO = polyline.radius;
        }

        polyline.isDefaultConditionAvailable = (polyline.isDefaultConditionAvailable || (i === 1 && AO > 10));
        //console.log("isDefaultConditionAvailable", polyline.isDefaultConditionAvailable);

        const ED = polyline.getLineLengthY(i - 1) * polyline.radius / AO;
        const OD = polyline.getLineLengthX(i - 1) * polyline.radius / AO;
        targetX = anchor.x - OD;
        targetY = anchor.y - ED;

        if (AO < 2 * polyline.radius && i > 1) {
          targetX = anchor.x - polyline.getLineLengthX(i - 1) / 2;
          targetY = anchor.y - polyline.getLineLengthY(i - 1) / 2;
        }

        // pivot point of next line
        let AO1 = polyline.getLineLength(i);
        if (AO1 < polyline.radius) {
          AO1 = polyline.radius;
        }
        const ED1 = polyline.getLineLengthY(i) * polyline.radius / AO1;
        const OD1 = polyline.getLineLengthX(i) * polyline.radius / AO1;
        let nextSrcX = anchor.x + OD1;
        let nextSrcY = anchor.y + ED1;

        if (AO1 < 2 * polyline.radius && i < polyline.getAnchorsCount() - 2) {
          nextSrcX = anchor.x + polyline.getLineLengthX(i) / 2;
          nextSrcY = anchor.y + polyline.getLineLengthY(i) / 2;
        }


        dx0 = (cx - targetX) / 3;
        dy0 = (cy - targetY) / 3;
        ax = cx - dx0;
        ay = cy - dy0;
        dx1 = (cx - nextSrcX) / 3;
        dy1 = (cy - nextSrcY) / 3;
        bx = cx - dx1;
        by = cy - dy1;
        zx = nextSrcX;
        zy = nextSrcY;

        if (polyline.showDetails) {
          this.g.path("M" + targetX + "," + targetY + "L" + ax + "," + ay).attr({
            stroke: RaphaelOriginal.getRGB("rgb(255, 153, 51)"),
            "stroke-dasharray": "- "
          });
          this.g.path("M" + nextSrcX + "," + nextSrcY + "L" + bx + "," + by).attr({
            stroke: RaphaelOriginal.getRGB("rgb(255, 153, 51)"),
            "stroke-dasharray": "- "
          });
          this.g.ellipse(ax, ay, 2, 2).attr({stroke: Color.SlateGrey});
          this.g.ellipse(bx, by, 2, 2).attr({stroke: Color.SlateGrey});
          this.g.ellipse(cx, cy, polyline.radius, polyline.radius).attr({stroke: Color.Gainsboro});
          this.g.ellipse(targetX, targetY, 2, 2).attr({fill: Color.red});
          this.g.ellipse(nextSrcX, nextSrcY, 2, 2).attr({fill: Color.red});
        }
      } else if (i === 1 && polyline.getAnchorsCount() === 2) {
        let AO1 = polyline.getLineLength(i - 1);
        if (AO1 < polyline.radius) {
          AO1 = polyline.radius;
        }
        polyline.isDefaultConditionAvailable = (polyline.isDefaultConditionAvailable || (i === 1 && AO1 > 10));
        //console.log("-- isDefaultConditionAvailable", polyline.isDefaultConditionAvailable);
      }

      // anti smoothing
      if (polyline.strokeWidth % 2 === 1) {
        targetX += 0.5;
        targetY += 0.5;
      }

      path.push([pathType, targetX, targetY]);

      if (i > 0 && i < polyline.getAnchorsCount() - 1) {
        path.push(["C", ax, ay, bx, by, zx, zy]);
      }
    }

    if (polyline.closePath) {
      console.log("closePath:", polyline.closePath);
      path.push(["Z"]);
    }

    polyline.path = path;
  };

  @autobind
  _setFont(font) {
    this.font = font;
  }

  @autobind
  _getFont() {
    return this.font;
  }

  @autobind
  _setStroke(strokeWidth) {
    this.strokeWidth = strokeWidth;
  }

  @autobind
  _getStroke() {
    return this.strokeWidth;
  }

  @autobind
  _setPaint(color) {
    this.paint = color;
  }

  @autobind
  _getPaint() {
    return this.paint;
  }

  @autobind
  getSvgSize(dataSource) {
    let minX = 0;
    let maxX = 0;
    let minY = 0;
    let maxY = 0;

    const activities = dataSource.activities;
    let minOffsetTop = 10000;
    activities.forEach(item => {
      if (item.y < minOffsetTop) {
        minOffsetTop = item.y;
      }
    });
    minOffsetTop -= 30;
    activities.map(item => {
      item.y -= minOffsetTop;
      return item;
    });
    activities.forEach((item, i) => {
      const activityJson = item;
      const activity = new ActivityImpl(activityJson);

      // width
      if (activity.getX() + activity.getWidth() > maxX) {
        maxX = activity.getX() + activity.getWidth();
      }
      if (activity.getX() < minX) {
        minX = activity.getX();
      }
      // height
      if (activity.getY() + activity.getHeight() > maxY) {
        maxY = activity.getY() + activity.getHeight();
      }
      if (activity.getY() < minY) {
        minY = activity.getY();
      }
    });

    const sequenceFlows = dataSource.sequenceFlows;
    sequenceFlows.map(item => {
      item.yPointArray[0] -= minOffsetTop;
      item.yPointArray[1] -= minOffsetTop;
      return item;
    });
    sequenceFlows.forEach((item, i) => {
      const flow = item;
      const waypoints = [];
      flow.xPointArray.forEach((item1, j) => {
        waypoints[j] = { x: flow.xPointArray[j], y: flow.yPointArray[j] };
        // width
        if (waypoints[j].x > maxX) {
          maxX = waypoints[j].x;
        }
        if (waypoints[j].x < minX) {
          minX = waypoints[j].x;
        }
        // height
        if (waypoints[j].y > maxY) {
          maxY = waypoints[j].y;
        }
        if (waypoints[j].y < minY) {
          minY = waypoints[j].y;
        }
      });
    });

    return { maxX: maxX +20, maxY: maxY + 20 };
  }

  @autobind
  messageClick() {

  }

  @autobind
  renderActivityList(taskList) {
    if (taskList.length === 0) {
      return (
        <div className="activityInfoEmpty">暂无进度流转！</div>
      );
    }
    // taskList.push(this.state.taskList[0]);
    // taskList.push(this.state.taskList[0]);
    // taskList.push(this.state.taskList[0]);
    this.timeLine = [];
    const radius = this.state.radius;
    return (
      <div style={{ display: 'flex' }}>
        <div id="canvasBox" style={{ width: 40 }}>
          <canvas id="timeLineCanvas" />
        </div>
        <div style={{ width: 'calc(100% - 40px)' }}>
          {taskList.map((item, i) => {
            this.timeLine.push({type: "circle", y: 300 * i + 14});
            if (item.endTime) {
              this.timeLine.push({
                type: "line",
                startY: this.timeLine[this.timeLine.length - 1].y + radius,
                endY: this.timeLine[this.timeLine.length - 1].y + 244 + 14 + 14 - radius
              });
              this.timeLine.push({type: "circle", y: this.timeLine[this.timeLine.length - 1].endY + radius});
            } else {
              this.timeLine.push({
                type: "line",
                startY: this.timeLine[this.timeLine.length - 1].y + radius,
                endY: this.timeLine[this.timeLine.length - 1].y + 300 - 14
              });
            }
            return (
              <div className="activityInfoItem">
                <span style={{ lineHeight: '28px' }}>{item.startTime}  开始承办时间</span>
                <div style={{ height: 'calc(100% - 56px)' }}>
                  <div className="activityInfoItemCard" style={{ minHeight: 58, border: '1px solid #C1C1C1', borderRadius: 4, marginBottom: 4 }}>
                    {item.frontAssigneeName ?
                      (
                        <div>
                          <div style={{ lineHeight: '28px', marginLeft: 10 }}>前手信息：</div>
                          <div style={{ height: 'calc(100% - 28px)', display: 'flex', alignItems: 'center', marginLeft: 30, fontSize: 15 }}>{item.frontAssigneeName}</div>
                        </div>
                      ) :
                      (
                        <span style={{ lineHeight: '60px', marginLeft: 10, fontSize: 15 }}>没有前手信息！</span>
                      )
                    }
                  </div>
                  <div style={{ height: 30, display: 'flex', alignItems: 'center', justifyContent: 'center' }}><img src={arrow} /></div>
                  <div className="activityInfoItemCard" style={{ minHeight: 58, display: 'flex', border: '1px solid #C1C1C1', borderRadius: 4 }}>
                    <div style={{ width: 64, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
                      <img src={user_normal} />
                    </div>
                    <div style={{ width: 'calc(100% - 64px)' }}>
                      <div style={{ height: '50%', display: 'flex', alignItems: 'center' }}><i>{item.assigneeName}</i></div>
                      <div style={{ height: '50%', display: 'flex', alignItems: 'center' }}><i>{item.deptName}</i></div>
                    </div>
                  </div>
                  <div style={{ height: 30, display: 'flex', alignItems: 'center', justifyContent: 'center' }}><img src={arrow} /></div>
                  <div className="activityInfoItemCard" style={{ minHeight: 58, border: '1px solid #C1C1C1', borderRadius: 4, marginTop: 4 }}>
                    {item.endTime ?
                      (item.afterAssigneeName ?
                        (
                          <div>
                            <div style={{ lineHeight: '28px', marginLeft: 10 }}>后手信息：</div>
                            <div style={{ height: 'calc(100% - 28px)', display: 'flex', alignItems: 'center', marginLeft: 30, fontSize: 15 }}>{item.afterAssigneeName}</div>
                          </div>
                        ) :
                        (<span style={{ lineHeight: '56px', marginLeft: 10, fontSize: 15 }}>没有后手信息</span>)) :
                      (
                        <span style={{ lineHeight: '56px', marginLeft: 10, fontSize: 15 }}>待审核...</span>
                      )
                    }
                  </div>
                </div>
                <div style={{ height: 28, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
                  {item.endTime && (<span>{item.endTime}  结束承办时间</span>)}
                  {item.endTime && item.frontAssigneeName && (
                    <div style={{ display: 'flex', alignItems: 'center' }}>
                      <img style={{ width: 18, height: 18 }} src={massage} />
                      {taskList[0].frontAssigneeName && (
                        <ExtendButton
                          type="link"
                          inline={false}
                          buttonProps={{ type: 'ghost' }}
                          {...{
                            buttonDescription: "查看意见",
                            hideIfReadonly: false,
                            interactiveType: "MODAL",
                            messagePromptType: "NONE",
                            actionLink: "/Api/BPM/RetrieveSpecificTaskIdFormValue",
                            nextActionLink: null,
                            templateType: "Detail",
                            actionName: null,
                            buttonCode: "creditLineAssessmentApplyInfo_Detail_render_In_creditLineAssessmentApplyInfo_Detail",
                            transmitParameters: [
                              {
                                name: "processInstanceId"
                              },{
                                name: "taskId"
                              },{
                                name: "other"
                              },{
                                name: "taskDefinitionKey"
                              }
                            ],
                            bindParameterType: null,
                            bindParameters: null,
                            actionType: "ROUTER",
                            relatedItems: null,
                            requestSuccessDesc: null,
                            isTransmitAllParameters: false,
                            relatedData: "NONE"
                          }}
                          disabled={false}
                          key='查看意见'
                          noneIcon={true}
                          query={{ processInstanceId: item.processInstanceId, taskId: item.taskId, other: 'taskHistory', taskDefinitionKey: item.taskDefinitionKey }}
                        />
                      )}
                      {/*<a className="msgIsExist" onClick={this.messageClick}>查看意见</a>*/}
                    </div>
                  )}
                </div>
              </div>
            );
          })}
        </div>
      </div>
    );
  }

  componentDidUpdate() {

    // const activityInfoItems =  document.getElementsByClassName("activityInfoItem");
    // for(let i = 0; i < activityInfoItems.length; i++) {
    //   const item = activityInfoItems[i];
    //   const activityInfoItemCards = item.getElementsByClassName("activityInfoItemCard");
    //   let itemHeights = [];
    //   for(let j = 0; j < activityInfoItemCards.length; j++) {
    //     itemHeights.push(parseInt(activityInfoItemCards[j].scrollHeight));
    //   }
    //
    //   for(let k = 0; k < activityInfoItemCards.length; k++) {
    //     activityInfoItemCards[k].style.height = Math.max(...itemHeights) + 'px';
    //   }
    // }

    const { radius, taskList } = this.state;
    if (taskList && taskList.length > 0) {
      // taskList.push(this.state.taskList[0]);
      // taskList.push(this.state.taskList[0]);
      // taskList.push(this.state.taskList[0]);
      this.timeLine = [];
      const itemHeights = [];
      taskList.forEach((item, i) => {
        const itemHeight =  document.getElementsByClassName("activityInfoItem")[i].clientHeight;
        const startHeight = itemHeights.slice(0, i + 1).reduce((a, b) => a + b, 0);
        itemHeights.push(itemHeight);
        this.timeLine.push({ type: 'circle', y: startHeight + 14 });
        if (item.endTime) {
          this.timeLine.push({
            type: 'line',
            startY: this.timeLine[this.timeLine.length - 1].y + radius,
            endY: this.timeLine[this.timeLine.length - 1].y + itemHeight - 56 + 14 + 14 - radius
          });
          this.timeLine.push({ type: 'circle', y: this.timeLine[this.timeLine.length - 1].endY + radius });
        } else {
          this.timeLine.push({
            type: 'line',
            startY: this.timeLine[this.timeLine.length - 1].y + radius,
            endY: this.timeLine[this.timeLine.length - 1].y + itemHeight - 14
          });
        }
      });

      const timeLineCanvas = document.getElementById("timeLineCanvas");
      const canvasBox = document.getElementById("canvasBox");
      const activityInfoContent = document.getElementById("activityInfoContent");
      const ctx = timeLineCanvas.getContext("2d");
      ctx.canvas.width = canvasBox.clientWidth;
      // canvasBox.style.height = activityInfoContent.clientHeight + 'px';
      const lastTimeLine = this.timeLine[this.timeLine.length - 1];
      // ctx.canvas.height = canvasBox.clientHeight;
      if (lastTimeLine.type === 'circle') {
        ctx.canvas.height = lastTimeLine.y + radius;
        canvasBox.style.height = lastTimeLine.y + radius + 'px';
      } else {
        ctx.canvas.height = lastTimeLine.endY;
        canvasBox.style.height = lastTimeLine.endY + 'px';
      }
      const lineArr = [];
      this.timeLine.forEach(item => {
        if (item.type === 'circle') {
          ctx.beginPath();
          ctx.arc(20, item.y, this.state.radius, 0, 2 * Math.PI);
          ctx.fillStyle = '#2B97E0';
          ctx.strokeStyle = '#2B97E0';
          ctx.fill();
          ctx.closePath();
        } else {
          lineArr.push(item);
        }
      });
      lineArr.forEach(item => {
        ctx.beginPath();
        ctx.strokeStyle = '#CCCCCC';
        ctx.moveTo(20, item.startY);
        ctx.lineTo(20, item.endY);
        ctx.stroke();
        ctx.closePath();
      });
    }
    document.querySelector('#activityList').scrollTop = 0;
  }

  @autobind
  burgerClick(firstClick) {
    if (this.activeActivity) {
      const activityInfo = document.getElementById("diagramActivityInfo");
      const holderBox = document.getElementById("diagramHolderBox");
      const activityList = document.getElementById("activityList");
      const activityInfoTitle = document.getElementById("activityInfoTitle");
      const diagramActivityInfoBtn = document.getElementById("diagramActivityInfoBtn");
      if (firstClick || activityInfo.style.width === '32px' || activityInfo.style.width === '0px' || activityInfo.style.width === '') {
        activityInfo.style.width = '26%';
        holderBox.style.width = 'calc(74% + 16px)';
        setTimeout(() => {
          diagramActivityInfoBtn.style.top = 0;
          activityList.style.visibility = "visible";
          activityInfoTitle.style.visibility = "visible";
        }, 200);
        setTimeout(() => {
          this.setState({
            closeBtnClassName: 'burger burgerClose'
          });
        }, 300);
      } else {
        diagramActivityInfoBtn.style.top = 'calc((100% - 16px) / 2)';
        setTimeout(() => {
          activityInfo.style.width = 0;
          holderBox.style.width = '100%';
          activityList.style.visibility = 'hidden';
          activityInfoTitle.style.visibility = 'hidden';
        }, 200);
        this.setState({
          closeBtnClassName: 'burger'
        });
      }
    }
  }

  @autobind
  burgerMouseOver() {
    const activityInfo = document.getElementById("diagramActivityInfo");
    if (activityInfo.style.width === '0px' || activityInfo.style.width === '') {
      activityInfo.style.width = '32px';
    }
  }

  @autobind
  burgerMouseOut() {
    const activityInfo = document.getElementById("diagramActivityInfo");
    if (activityInfo.style.width === '32px') {
      activityInfo.style.width = 0;
    }
  }

  @autobind
  holderMouseDown(event) {
    const holderBox = document.getElementById("diagramHolderBox");
    this.divLeft = parseInt(holderBox.scrollLeft, 10);
    this.divTop = parseInt(holderBox.scrollTop, 10);
    this.mousey = event.pageY;
    this.mousex = event.pageX;
    document.getElementById("diagramHolder").onmousemove = this.holderMouseMove;
  }

  @autobind
  holderMouseMove(event) {
    const holderBox = document.getElementById("diagramHolderBox");
    const left = this.divLeft - (event.pageX - this.mousex);
    const top1 = this.divTop - (event.pageY - this.mousey);
    holderBox.scrollTop = top1;
    holderBox.scrollLeft = left;
    return false;
  }

  @autobind
  holderMouseUp() {
    document.getElementById("diagramHolder").onmousemove = null;
  }

  @autobind
  zoomClick(e) {
    const zoomNum  = this.zoomNum;
    const svg = document.querySelector(".diagram svg");
    const diagramHolder = document.getElementById("diagramHolder");
    const diagram = document.getElementsByClassName("diagram")[0];
    const diagramHolderBox = document.getElementById("diagramHolderBox");
    if ((zoomNum >= -3 && zoomNum <= 3) || (zoomNum < -3 && e === '+') || (zoomNum > 3 && e === '-')) {
      if (!this.g._viewBox) {
        this.g.setViewBox(0, 0, parseInt(svg.getBoundingClientRect().width), parseInt(svg.getBoundingClientRect().height));
      }
      if (e === '-') {
        diagram.style.width = svg.getBoundingClientRect().width * 0.8;
        diagram.style.height = svg.getBoundingClientRect().height * 0.8;
        svg.setAttribute('width', svg.getBoundingClientRect().width * 0.8);
        svg.setAttribute('height', svg.getBoundingClientRect().height * 0.8);
        if (zoomNum > 0) {
          diagramHolder.style.width = (diagramHolder.clientWidth * 0.8) + 'px';
          diagramHolder.style.height = (diagramHolder.clientHeight * 0.8) + 'px';
          diagramHolderBox.scrollTop = (parseInt(diagramHolder.style.width) - diagramHolderBox.clientWidth) / 2;
          diagramHolderBox.scrollLeft = (parseInt(diagramHolder.style.height) - diagramHolderBox.clientHeight) / 2;
        }
        this.zoomNum -= 1;
      } else {
        diagram.style.width = (svg.getBoundingClientRect().width / 0.8) + 'px';
        diagram.style.height = (svg.getBoundingClientRect().height / 0.8) + 'px';
        svg.setAttribute('width', svg.getBoundingClientRect().width / 0.8);
        svg.setAttribute('height', svg.getBoundingClientRect().height / 0.8);
        if (zoomNum >= 0) {
          diagramHolder.style.width = (diagramHolder.clientWidth / 0.8) + 'px';
          diagramHolder.style.height = (diagramHolder.clientHeight / 0.8) + 'px';
          diagramHolderBox.scrollTop = (parseInt(diagramHolder.style.width) - diagramHolderBox.clientWidth) / 2;
          diagramHolderBox.scrollLeft = (parseInt(diagramHolder.style.height) - diagramHolderBox.clientHeight) / 2;
        }
        this.zoomNum += 1;
      }
    }
  }

  /* eslint-disable */
  render() {
    const dataSource = this.props.dataSource;
    const { svgSize, taskList, closeBtnClassName } = this.state;
    const diagramStyle = {
      width: svgSize.maxX,
      height: svgSize.maxY
    };
    return (
      <div id="svgWrapper">
        <div style={{ position: 'fixed', top: 0, left: 0, width: '100%' }}>
          <TitleBar getTitleDesc={() => dataSource && dataSource.processDefinition && dataSource.processDefinition.name ? '进度查询-' + dataSource.processDefinition.name : '进度查询'} />
        </div>
        <div id="contentBox" className="contentBox">
          <div className="diagramTitle" id="diagramTitle">
            <span style={{ fontSize: 14 }}>总体流程图</span>
            <div style={{ display: 'flex', justifyContent: 'space-between', marginRight: 20 }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginRight: 20 }}>
                <div style={{ width: 24, height: 8, backgroundColor: '#FFA94B', marginRight: 10 }}>

                </div>
                <div style={{ fontSize: 14 }}>当前进度</div>
              </div>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                <div style={{ width: 24, height: 8, backgroundColor: '#2B97E0', marginRight: 10 }}>

                </div>
                <div style={{ fontSize: 14 }}>查询进度</div>
              </div>
            </div>
          </div>
          <div style={{ position: 'absolute', top: 60, left: 10, zIndex: 110 }}>
            <div style={{ cursor: 'pointer', width: 20, height: 20, backgroundColor: '#FFFFFF', display: 'flex', justifyContent: 'center', alignItems: 'center' }} onClick={() => { this.zoomClick("+") }}>+</div>
            <div style={{ fontSize: 18, cursor: 'pointer', width: 20, height: 20, backgroundColor: '#FFFFFF', marginTop: 6, display: 'flex', justifyContent: 'center', alignItems: 'center' }} onClick={() => { this.zoomClick("-") }}>-</div>
          </div>
          <div id="diagramHolderBox" className="diagramHolderBox">
            <div id="diagramHolder" className="diagramHolder" onMouseDown={this.holderMouseDown} onMouseUp={this.holderMouseUp}>
              <div id={dataSource.processDefinition.name} className="diagram" style={diagramStyle}>
              </div>
            </div>
            <div style={{ width: 18, height: '100%', position: 'absolute', top: 0, right: -18, backgroundColor: '#fafafa', zIndex: 101 }}>

            </div>
          </div>
          <div className="diagramActivityInfo" id="diagramActivityInfo">
            <div id="activityInfoTitle" className="activityInfoTitle">进度流转情况</div>
            <div className="diagramActivityInfoBtn" id="diagramActivityInfoBtn" onMouseOut={this.burgerMouseOut} onMouseOver={this.burgerMouseOver} onClick={() => this.burgerClick()}>
              <div className={closeBtnClassName} id="burger">
                <div className="burgerPatty">

                </div>
                <div className="burgerPatty">

                </div>
                <div className="burgerPatty">

                </div>
              </div>
            </div>
            <div className="activityList" id="activityList">
              <div id="activityInfoContent" style={{ marginTop: 40 }}>
                {taskList && this.renderActivityList(taskList)}
              </div>
            </div>
          </div>
        </div>
      </div>
    );
  }
}
