/**
 * 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) {
                let errorUrlParams = errorUrl.split("/");
                if (errorUrlParams.length > 0)  errorUrlParams = errorUrlParams[errorUrlParams.length - 1];
                 // Check if the default img (table.png) path has error, if yes then stop recursion.
                if (errorUrlParams === "table.png")  return null;//removed condition for default process image

                var isErrorInTypeName = errorUrl && errorUrl.match("entity-icon/" + typeName + ".png|disabled/" + typeName + ".png") ? true : false;
                if (serviceType && isErrorInTypeName) {
                    var imageName = serviceType + ".png";
                    return getImgPath(imageName);
                } else {
                    return getDefaultImgPath();
                }
            } else if (typeName) {
                var imageName = typeName + ".png";
                return getImgPath(imageName);
            } else if (serviceType) {
                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) {
                                            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) {
                                            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;