/**
 * 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.
 */

import { select, event } from "d3-selection";
import { zoom, zoomIdentity } from "d3-zoom";
import { drag } from "d3-drag";
import { line, curveBasis } from "d3-shape";

import platform from "platform";

import Enums from "../Enums";

const LineageUtils = {
  /**
   * [nodeArrowDistance variable use to define the distance between arrow and node]
   * @type {Number}
   */
  nodeArrowDistance: 24,
  refreshGraphForSafari: function (options) {
    var edgePathEl = options.edgeEl,
      IEGraphRenderDone = 0;
    edgePathEl.each(function (argument) {
      var eleRef = this,
        childNode = $(this).find("pattern");
      setTimeout(function (argument) {
        $(eleRef).find("defs").append(childNode);
      }, 500);
    });
  },
  refreshGraphForIE: function ({ edgePathEl }) {
    var IEGraphRenderDone = 0;
    edgePathEl.each(function (argument) {
      var childNode = $(this).find("marker");
      $(this).find("marker").remove();
      var eleRef = this;
      ++IEGraphRenderDone;
      setTimeout(function (argument) {
        $(eleRef).find("defs").append(childNode);
        --IEGraphRenderDone;
        if (IEGraphRenderDone === 0) {
          this.$(".fontLoader").hide();
          this.$("svg").fadeTo(1000, 1);
        }
      }, 1000);
    });
  },
  /**
   * [dragNode description]
   * @param  {[type]} options.g          [description]
   * @param  {[type]} options.svg        [description]
   * @param  {[type]} options.guid       [description]
   * @param  {[type]} options.edgePathEl [description]
   * @return {[type]}                    [description]
   */
  dragNode: function ({ g, svg, guid, edgePathEl }) {
    var dragHelper = {
      dragmove: function (el, d) {
        var node = select(el),
          selectedNode = g.node(d),
          prevX = selectedNode.x,
          prevY = selectedNode.y;

        selectedNode.x += event.dx;
        selectedNode.y += event.dy;
        node.attr(
          "transform",
          "translate(" + selectedNode.x + "," + selectedNode.y + ")"
        );

        var dx = selectedNode.x - prevX,
          dy = selectedNode.y - prevY;

        g.edges().forEach((e) => {
          if (e.v == d || e.w == d) {
            var edge = g.edge(e.v, e.w);
            this.translateEdge(edge, dx, dy);
            select(edge.elem).select("path").attr("d", this.calcPoints(e));
          }
        });
        //LineageUtils.refreshGraphForIE({ edgePathEl: edgePathEl });
      },
      translateEdge: function (e, dx, dy) {
        e.points.forEach(function (p) {
          p.x = p.x + dx;
          p.y = p.y + dy;
        });
      },
      calcPoints: function (e) {
        var edge = g.edge(e.v, e.w),
          tail = g.node(e.v),
          head = g.node(e.w),
          points = edge.points.slice(1, edge.points.length - 1),
          afterslice = edge.points.slice(1, edge.points.length - 1);
        points.unshift(this.intersectRect(tail, points[0]));
        points.push(this.intersectRect(head, points[points.length - 1]));
        return line()
          .x(function (d) {
            return d.x;
          })
          .y(function (d) {
            return d.y;
          })
          .curve(curveBasis)(points);
      },
      intersectRect: (node, point) => {
        var x = node.x,
          y = node.y,
          dx = point.x - x,
          dy = point.y - y,
          nodeDistance = guid
            ? this.nodeArrowDistance + 3
            : this.nodeArrowDistance,
          w = nodeDistance,
          h = nodeDistance,
          sx = 0,
          sy = 0;

        if (Math.abs(dy) * w > Math.abs(dx) * h) {
          // Intersection is top or bottom of rect.
          if (dy < 0) {
            h = -h;
          }
          sx = dy === 0 ? 0 : (h * dx) / dy;
          sy = h;
        } else {
          // Intersection is left or right of rect.
          if (dx < 0) {
            w = -w;
          }
          sx = w;
          sy = dx === 0 ? 0 : (w * dy) / dx;
        }
        return {
          x: x + sx,
          y: y + sy,
        };
      },
    };
    var dragNodeHandler = drag().on("drag", function (d) {
        dragHelper.dragmove.call(dragHelper, this, d);
      }),
      dragEdgePathHandler = drag().on("drag", function (d) {
        dragHelper.translateEdge(g.edge(d.v, d.w), event.dx, event.dy);
        var edgeObj = g.edge(d.v, d.w);
        select(edgeObj.elem).select("path").attr("d", dragHelper.calcPoints(d));
      });

    dragNodeHandler(svg.selectAll("g.node"));
    dragEdgePathHandler(svg.selectAll("g.edgePath"));
  },
  zoomIn: function ({ svg, scaleFactor = 1.3 }) {
    this.d3Zoom.scaleBy(svg.transition().duration(750), scaleFactor);
  },
  zoomOut: function ({ svg, scaleFactor = 0.8 }) {
    this.d3Zoom.scaleBy(svg.transition().duration(750), scaleFactor);
  },
  zoom: function ({ svg, xa, ya, scale }) {
    svg
      .transition()
      .duration(750)
      .call(this.d3Zoom.transform, zoomIdentity.translate(xa, ya).scale(scale));
  },
  fitToScreen: function ({ svg }) {
    var node = svg.node();
    var bounds = node.getBBox();

    var parent = node.parentElement,
      fullWidth = parent.clientWidth,
      fullHeight = parent.clientHeight;

    var width = bounds.width,
      height = bounds.height;
    var midX = bounds.x + width / 2,
      midY = bounds.y + height / 2;

    var scale =
        (scale || 0.95) / Math.max(width / fullWidth, height / fullHeight),
      xa = fullWidth / 2 - scale * midX,
      ya = fullHeight / 2 - scale * midY;
    this.zoom({ svg, xa, ya, scale });
  },
  /**
   * [centerNode description]
   * @param  {[type]} options.guid           [description]
   * @param  {[type]} options.g              [description]
   * @param  {[type]} options.svg            [description]
   * @param  {[type]} options.svgGroupEl     [description]
   * @param  {[type]} options.edgePathEl     [description]
   * @param  {[type]} options.width          [description]
   * @param  {[type]} options.height         [description]
   * @param  {[type]} options.onCenterZoomed [description]
   * @return {[type]}                        [description]
   */
  centerNode: function ({
    guid,
    g,
    svg,
    svgGroupEl,
    edgePathEl,
    width,
    height,
    fitToScreen,
    onCenterZoomed,
    isSelected,
  }) {
    this.d3Zoom = zoom();
    svg.call(this.d3Zoom).on("dblclick.zoom", null);

    // restrict events

    let selectedNodeEl = svg.selectAll("g.nodes>g[id='" + guid + "']"),
      zoomListener = this.d3Zoom
        .scaleExtent([0.01, 50])
        .on("zoom", function () {
          svgGroupEl.attr("transform", event.transform);
        }),
      x = null,
      y = null,
      scale = 1.2;
    if (selectedNodeEl.empty()) {
      if (fitToScreen) {
        this.fitToScreen({ svg });
        return;
      } else {
        x = g.graph().width / 2;
        y = g.graph().height / 2;
      }
    } else {
      var matrix = selectedNodeEl
        .attr("transform")
        .replace(/[^0-9\-.,]/g, "")
        .split(",");
      // if (platform.name === "IE" || platform.name === "Microsoft Edge") {
      //     var matrix = selectedNode
      //         .attr("transform")
      //         .replace(/[a-z\()]/g, "")
      //         .split(" ");
      // }
      x = matrix[0];
      y = matrix[1];
    }

    var xa = -(x * scale - width / 2),
      ya = -(y * scale - height / 2);
    this.zoom({ svg, xa, ya, scale });

    if (!isSelected) {
      svg.call(
        this.d3Zoom.transform,
        zoomIdentity.translate(xa, ya).scale(scale)
      );
    } else {
      svg
        .transition()
        .duration(750)
        .call(
          this.d3Zoom.transform,
          zoomIdentity.translate(xa, ya).scale(scale)
        );
    }

    if (onCenterZoomed) {
      onCenterZoomed({
        newScale: scale,
        newTranslate: [xa, ya],
        d3Zoom: this.d3Zoom,
        selectedNodeEl,
      });
    }
    // if (platform.name === "IE") {
    //     LineageUtils.refreshGraphForIE({ edgePathEl: edgePathEl });
    // }
  },
  /**
   * [getToolTipDirection description]
   * @param  {[type]} options.el [description]
   * @return {[type]}            [description]
   */
  getToolTipDirection: function ({ el }) {
    var width = select("body").node().getBoundingClientRect().width,
      currentELWidth = select(el).node().getBoundingClientRect(),
      direction = "e";
    if (width - currentELWidth.left < 330) {
      direction =
        width - currentELWidth.left < 330 && currentELWidth.top < 400
          ? "sw"
          : "w";
      if (width - currentELWidth.left < 330 && currentELWidth.top > 600) {
        direction = "nw";
      }
    } else if (currentELWidth.top > 600) {
      direction =
        width - currentELWidth.left < 330 && currentELWidth.top > 600
          ? "nw"
          : "n";
      if (currentELWidth.left < 50) {
        direction = "ne";
      }
    } else if (currentELWidth.top < 400) {
      direction = currentELWidth.left < 50 ? "se" : "s";
    }
    return direction;
  },
  /**
   * [onHoverFade description]
   * @param  {[type]} options.svg              [description]
   * @param  {[type]} options.g                [description]
   * @param  {[type]} options.mouseenter       [description]
   * @param  {[type]} options.opacity          [description]
   * @param  {[type]} options.nodesToHighlight [description]
   * @param  {[type]} options.hoveredNode      [description]
   * @return {[type]}                          [description]
   */
  onHoverFade: function ({
    svg,
    g,
    mouseenter,
    nodesToHighlight,
    hoveredNode,
  }) {
    var node = svg.selectAll(".node"),
      path = svg.selectAll(".edgePath"),
      isConnected = function (a, b, o) {
        if (a === o || (b && b.length && b.indexOf(o) != -1)) {
          return true;
        }
      };
    if (mouseenter) {
      svg.classed("hover", true);
      var nextNode = g.successors(hoveredNode),
        previousNode = g.predecessors(hoveredNode),
        nodesToHighlight = nextNode.concat(previousNode);
      node.classed("hover-active-node", function (currentNode, i, nodes) {
        if (isConnected(hoveredNode, nodesToHighlight, currentNode)) {
          return true;
        } else {
          return false;
        }
      });
      path.classed("hover-active-path", function (c) {
        var _thisOpacity = c.v === hoveredNode || c.w === hoveredNode ? 1 : 0;
        if (_thisOpacity) {
          return true;
        } else {
          return false;
        }
      });
    } else {
      svg.classed("hover", false);
      node.classed("hover-active-node", false);
      path.classed("hover-active-path", false);
    }
  },
  /**
   * [getBaseUrl description]
   * @param  {[type]} path [description]
   * @return {[type]}      [description]
   */
  getBaseUrl: function (url = window.location.pathname) {
    return url.replace(/\/[\w-]+.(jsp|html)|\/+$/gi, "");
  },
  getEntityIconPath: function ({ entityData, errorUrl, imgBasePath }) {
    var iconBasePath = this.getBaseUrl() + (imgBasePath || "/img/entity-icon/");
    if (entityData) {
      let { typeName, serviceType, status, isProcess } = entityData;

      function getImgPath(imageName) {
        return (
          iconBasePath +
          (Enums.entityStateReadOnly[status]
            ? "disabled/" + imageName
            : imageName)
        );
      }

      function getDefaultImgPath() {
        if (isProcess) {
          if (Enums.entityStateReadOnly[status]) {
            return iconBasePath + "disabled/process.png";
          } else {
            return iconBasePath + "process.png";
          }
        } else {
          if (Enums.entityStateReadOnly[status]) {
            return iconBasePath + "disabled/table.png";
          } else {
            return iconBasePath + "table.png";
          }
        }
      }

      if (errorUrl) {
        // Check if the default img path has error, if yes then stop recursion.
        let errorUrlParams = errorUrl.split("/");
        if (errorUrlParams.length > 0)  errorUrlParams = errorUrlParams[errorUrlParams.length - 1];
        if (errorUrlParams === "table.png")  return null;
        var isErrorInTypeName =
          errorUrl &&
          errorUrl.match(
            "entity-icon/" + typeName + ".png|disabled/" + typeName + ".png"
          )
            ? true
            : false;
        if (serviceType && isErrorInTypeName) {
          // var imageName = serviceType.toLowerCase() + ".png";
          var imageName = serviceType + ".png";
          return getImgPath(imageName);
        } else {
          return getDefaultImgPath();
        }
      } else if (typeName) {
        // var imageName = typeName.toLowerCase() + ".png";
        var imageName = typeName + ".png";
        return getImgPath(imageName);
      } else if (serviceType) {
        // var imageName = serviceType.toLowerCase() + ".png";
        var imageName = serviceType + ".png";
        return getImgPath(imageName);
      } else {
        return getDefaultImgPath();
      }
    }
  },
  base64Encode: function (file, callback) {
    const reader = new FileReader();
    reader.addEventListener("load", () => callback(reader.result));
    reader.readAsDataURL(file);
  },
  imgShapeRender: function (
    parent,
    bbox,
    node,
    { dagreD3, defsEl, imgBasePath, guid, isRankdirToBottom }
  ) {
    var that = this,
      viewGuid = guid,
      imageIconPath = node.btnType
        ? "/img/entity-icon/expandBtn.svg"
        : this.getEntityIconPath({ entityData: node, imgBasePath }),
      imgName = imageIconPath.split("/").pop();
    if (this.imageObject === undefined) {
      this.imageObject = {};
    }
    if (node.isDeleted) {
      imgName = "deleted_" + imgName;
    }
    if (node.id == viewGuid) {
      var currentNode = true;
    }
    var shapeSvg = parent
      .append("circle")
      .attr("fill", "url(#img_" + imgName + ")")
      .attr("r", isRankdirToBottom ? "30px" : "24px")
      .attr("data-stroke", node.id)
      .attr("stroke-width", "2px")
      .attr(
        "class",
        "nodeImage " +
          (currentNode ? "currentNode" : node.isProcess ? "process" : "node")
      );
    if (currentNode) {
      shapeSvg.attr("stroke", "#fb4200");
    }
    if (node.isIncomplete === true) {
      parent.attr("class", "node isIncomplete show");
      parent
        .insert("rect")
        .attr("x", "-5")
        .attr("y", "-23")
        .attr("width", "14")
        .attr("height", "16")
        .attr("fill", "url(#img_hourglass.svg)")
        .attr("data-stroke", node.id)
        .attr("stroke-width", "2px");

      var patternConfigShellIcon = {
        imgName: "hourglass.svg",
        imageIconPath: "/img/entity-icon/hourglass.svg",
        leftPosition: "0",
        topPosition: "0",
        width: "12",
        height: "14",
      };
      svgPattern(patternConfigShellIcon);
    }
    var patternConfigEntityIcon = {
      imgName: imgName,
      imageIconPath: imageIconPath,
      leftPosition: isRankdirToBottom ? "11" : "4",
      topPosition: isRankdirToBottom ? "20" : currentNode ? "3" : "4",
      width: "40",
      height: "40",
    };
    svgPattern(patternConfigEntityIcon);

    function svgPattern(patternConfig) {
      if (
        defsEl.select('pattern[id="img_' + patternConfig.imgName + '"]').empty()
      ) {
        defsEl
          .append("pattern")
          .attr("x", "0%")
          .attr("y", "0%")
          .attr("patternUnits", "objectBoundingBox")
          .attr("id", "img_" + patternConfig.imgName)
          .attr("width", "100%")
          .attr("height", "100%")
          .append("image")
          .attr("href", function (d) {
            var imgEl = this;
            if (node) {
              var getImageData = function (options) {
                var imagePath = options.imagePath,
                  ajaxOptions = {
                    url: imagePath,
                    method: "GET",
                    cache: true,
                  };

                // if (platform.name !== "IE") {
                //     ajaxOptions["mimeType"] = "text/plain; charset=x-user-defined";
                // }
                shapeSvg.attr("data-iconpath", imagePath);
                var xhr = new XMLHttpRequest();
                xhr.onreadystatechange = function () {
                  if (xhr.readyState === 4) {
                    if (
                      xhr.status === 200 &&
                      (xhr.response.type == "image/png" ||
                        xhr.response.type == "image/svg+xml")
                    ) {
                      if (platform.name !== "IE") {
                        that.base64Encode(this.response, (url) => {
                          that.imageObject[patternConfig.imageIconPath] = url;
                          select(imgEl).attr("xlink:href", url);
                        });
                      } else {
                        that.imageObject[patternConfig.imageIconPath] =
                          imagePath;
                      }
                      if (
                        patternConfig.imageIconPath !==
                        shapeSvg.attr("data-iconpath")
                      ) {
                        shapeSvg.attr(
                          "data-iconpathorigin",
                          patternConfig.imageIconPath
                        );
                      }
                    } else if (
                      xhr.status === 404 ||
                      xhr.response.type == "text/html"
                    ) {
                      const imgPath = that.getEntityIconPath({
                        entityData: node,
                        errorUrl: imagePath,
                      });
                      if (imgPath === null) {
                        const patternEL = select(imgEl.parentElement);
                        patternEL.select("image").remove();
                        patternEL
                          .attr("patternContentUnits", "objectBoundingBox")
                          .append("circle")
                          .attr("r", "24px")
                          .attr("fill", "#e8e8e8");
                      } else {
                        getImageData({
                          imagePath: imgPath,
                        });
                      }
                    }
                  }
                };
                xhr.responseType = "blob";
                xhr.open(ajaxOptions.method, ajaxOptions.url, true);
                xhr.send(null);
              };
              getImageData({
                imagePath: patternConfig.imageIconPath,
              });
            }
          })
          .attr("x", patternConfig.leftPosition)
          .attr("y", patternConfig.topPosition)
          .attr("width", patternConfig.width)
          .attr("height", patternConfig.height);
      }
    }

    node.intersect = function (point) {
      return dagreD3.intersect.circle(
        node,
        currentNode ? that.nodeArrowDistance + 3 : that.nodeArrowDistance,
        point
      );
    };
    return shapeSvg;
  },
  /**
   * [arrowPointRender description]
   * @param  {[type]} {parent, id,           edge, type, viewOptions [description]
   * @return {[type]}           [description]
   */
  arrowPointRender: function (parent, id, edge, type, { dagreD3 }) {
    var node = parent.node(),
      parentNode = node ? node.parentNode : parent;
    select(parentNode)
      .select("path.path")
      .attr("marker-end", "url(#" + id + ")");
    var marker = parent
      .append("marker")
      .attr("id", id)
      .attr("viewBox", "0 0 10 10")
      .attr("refX", 8)
      .attr("refY", 5)
      .attr("markerUnits", "strokeWidth")
      .attr("markerWidth", 4)
      .attr("markerHeight", 4)
      .attr("orient", "auto");

    var path = marker
      .append("path")
      .attr("d", "M 0 0 L 10 5 L 0 10 z")
      .style("fill", edge.styleObj.stroke);
    dagreD3.util.applyStyle(path, edge[type + "Style"]);
  },
  /**
   * [saveSvg description]
   * @param  {[type]} options.svg              [description]
   * @param  {[type]} options.width            [description]
   * @param  {[type]} options.height           [description]
   * @param  {[type]} options.downloadFileName [description]
   * @param  {[type]} options.onExportLineage  [description]
   * @return {[type]}                          [description]
   */
  saveSvg: function ({
    svg,
    width,
    height,
    downloadFileName,
    onExportLineage,
  }) {
    var that = this,
      svgClone = svg.clone(true).node(),
      scaleFactor = 1;
    setTimeout(function () {
      if (platform.name === "Firefox") {
        svgClone.setAttribute("width", width);
        svgClone.setAttribute("height", height);
      }
      const hiddenSvgEl = select("body").append("div");
      hiddenSvgEl.classed("hidden-svg", true);
      hiddenSvgEl.node().appendChild(svgClone);

      const svgCloneEl = select(".hidden-svg svg");
      svgCloneEl.select("g").attr("transform", "scale(" + scaleFactor + ")");
      svgCloneEl.select("foreignObject").remove();

      var canvasOffset = { x: 150, y: 150 },
        setWidth = svgClone.getBBox().width + canvasOffset.x,
        setHeight = svgClone.getBBox().height + canvasOffset.y,
        xAxis = svgClone.getBBox().x,
        yAxis = svgClone.getBBox().y;
      svgClone.attributes.viewBox.value =
        xAxis + "," + yAxis + "," + setWidth + "," + setHeight;

      var canvas = document.createElement("canvas");
      canvas.id = "canvas";
      canvas.style.display = "none";
      canvas.width = svgClone.getBBox().width * scaleFactor + canvasOffset.x;
      canvas.height = svgClone.getBBox().height * scaleFactor + canvasOffset.y;

      // Append Canvas in DOM
      select("body").node().appendChild(canvas);

      var ctx = canvas.getContext("2d"),
        data = new XMLSerializer().serializeToString(svgClone),
        DOMURL = window.URL || window.webkitURL || window;

      ctx.fillStyle = "#FFFFFF";
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      ctx.strokeRect(0, 0, canvas.width, canvas.height);
      ctx.restore();

      var img = new Image(canvas.width, canvas.height);
      var svgBlob = new Blob([data], { type: "image/svg+xml;base64" });
      if (platform.name === "Safari") {
        svgBlob = new Blob([data], { type: "image/svg+xml" });
      }
      ctx.drawImage(img, 50, 50, canvas.width, canvas.height);
      var url = DOMURL.createObjectURL(svgBlob);

      img.onload = function () {
        try {
          var a = document.createElement("a");
          a.download = downloadFileName;
          document.body.appendChild(a);
          ctx.drawImage(img, 50, 50, canvas.width, canvas.height);
          canvas.toBlob(function (blob) {
            if (!blob) {
              onExportLineage({
                status: "failed",
                message: "There was an error in downloading Lineage!",
              });
              return;
            }
            a.href = DOMURL.createObjectURL(blob);
            if (blob.size > 10000000) {
              onExportLineage({
                status: "failed",
                message:
                  "The Image size is huge, please open the image in a browser!",
              });
            }
            a.click();
            onExportLineage({ status: "Success", message: "Successful" });
            if (platform.name === "Safari") {
              that.refreshGraphForSafari({
                edgeEl: that.$("svg g.node"),
              });
            }
          }, "image/png");
          hiddenSvgEl.remove();
          canvas.remove();
        } catch (err) {
          onExportLineage({
            status: "failed",
            message: "There was an error in downloading Lineage!",
          });
        }
      };
      img.src = url;
    }, 0);
  },
};
export default LineageUtils;
