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

define(['require',
    'backbone',
    'hbs!tmpl/graph/LineageLayoutView_tmpl',
    'collection/VLineageList',
    'models/VEntity',
    'utils/Utils',
    'dagreD3',
    'd3-tip',
    'utils/Enums',
    'utils/UrlLinks',
    'utils/Globals',
    'platform',
    'jquery-ui'
], function(require, Backbone, LineageLayoutViewtmpl, VLineageList, VEntity, Utils, dagreD3, d3Tip, Enums, UrlLinks, Globals, platform) {
    'use strict';

    var LineageLayoutView = Backbone.Marionette.LayoutView.extend(
        /** @lends LineageLayoutView */
        {
            _viewName: 'LineageLayoutView',

            template: LineageLayoutViewtmpl,
            className: "resizeGraph",

            /** Layout sub regions */
            regions: {},

            /** ui selector cache */
            ui: {
                graph: ".graph",
                checkHideProcess: "[data-id='checkHideProcess']",
                checkDeletedEntity: "[data-id='checkDeletedEntity']",
                selectDepth: 'select[data-id="selectDepth"]',
                fltrTogler: '[data-id="fltr-togler"]',
                lineageFilterPanel: '.lineage-fltr-panel',
                LineageFullscreenToggler: '[data-id="fullScreen-toggler"]'
            },

            /** ui events hash */
            events: function() {
                var events = {};
                events["click " + this.ui.checkHideProcess] = 'onCheckUnwantedEntity';
                events["click " + this.ui.checkDeletedEntity] = 'onCheckUnwantedEntity';
                events['change ' + this.ui.selectDepth] = 'onSelectDepthChange';
                events["click " + this.ui.fltrTogler] = 'onClickFiltrTogler';
                events["click " + this.ui.LineageFullscreenToggler] = 'onClickLineageFullscreenToggler';
                return events;
            },

            /**
             * intialize a new LineageLayoutView Layout
             * @constructs
             */
            initialize: function(options) {
                _.extend(this, _.pick(options, 'processCheck', 'guid', 'entityDefCollection', 'actionCallBack', 'fetchCollection'));
                this.collection = new VLineageList();
                this.lineageData = null;
                this.typeMap = {};
                this.apiGuid = {};
                this.asyncFetchCounter = 0;
                this.edgeCall;
                this.filterObj = {
                    isProcessHideCheck: false,
                    isDeletedEntityHideCheck: false,
                    depthCount: ''
                };
            },

            initializeGraph: function() {
                this.g = {};
                this.g = new dagreD3.graphlib.Graph()
                    .setGraph({
                        nodesep: 50,
                        ranksep: 90,
                        rankdir: "LR",
                        marginx: 20,
                        marginy: 20,
                        transition: function transition(selection) {
                            return selection.transition().duration(500);
                        }
                    })
                    .setDefaultEdgeLabel(function() {
                        return {};
                    });
            },

            onRender: function() {
                var that = this;
                this.fetchGraphData();
                if (platform.name === "IE") {
                    this.$('svg').css('opacity', '0');
                }
                if (this.layoutRendered) {
                    this.layoutRendered();
                }
                if (this.processCheck) {
                    this.hideCheckForProcess();
                }
                this.initializeGraph();
                this.ui.selectDepth.select2({
                    data: _.sortBy([3, 6, 9, 12, 15, 18, 21]),
                    tags: true,
                    dropdownCssClass: "number-input",
                    multiple: false
                });
            },
            onShow: function() {
                this.$('.fontLoader').show();
                this.$el.resizable({
                    handles: ' s',
                    minHeight: 375,
                    stop: function(event, ui) {
                        ui.element.height(($(this).height()));
                    },
                });
            },
            onClickLineageFullscreenToggler: function(e) {
                var icon = $(e.target).find('i'),
                    panel = $(e.target).parents('.tab-pane').first();
                icon.toggleClass('fa-expand fa-compress');
                panel.toggleClass('fullscreen-mode');
            },
            onCheckUnwantedEntity: function(e) {
                var data = $.extend(true, {}, this.lineageData);
                this.fromToObj = {};
                this.initializeGraph();
                if ($(e.target).data("id") === "checkHideProcess") {
                    this.filterObj.isProcessHideCheck = e.target.checked;
                } else {
                    this.filterObj.isDeletedEntityHideCheck = e.target.checked;
                }
                this.generateData(data.relations, data.guidEntityMap);
            },
            onClickFiltrTogler: function() {
                var lineageFilterPanel = this.ui.lineageFilterPanel;
                $(lineageFilterPanel).toggleClass("show-filter-panel");
            },

            onSelectDepthChange: function(e, options) {
                this.initializeGraph();
                this.filterObj.depthCount = e.currentTarget.value;
                this.fetchGraphData({ queryParam: { 'depth': this.filterObj.depthCount } });
            },

            fetchGraphData: function(options) {
                var that = this,
                    queryParam = options && options.queryParam || {};
                this.fromToObj = {};
                this.$('.fontLoader').show();
                this.$('svg>g').hide();
                this.collection.getLineage(this.guid, {
                    skipDefaultError: true,
                    queryParam: queryParam,
                    success: function(data) {
                        if (data.relations.length) {
                            that.lineageData = $.extend(true, {}, data)
                            that.generateData(data.relations, data.guidEntityMap);
                        } else {
                            that.noLineage();
                            that.hideCheckForProcess();
                        }
                    },
                    cust_error: function(model, response) {
                        that.lineageData = [];
                        that.noLineage();
                    },
                    complete: function() {
                        that.$('.fontLoader').hide();
                        that.$('svg>g').show();
                    }
                })
            },
            noLineage: function() {
                this.$('.fontLoader').hide();
                this.$('.depth-container').hide();
                //this.$('svg').height('100');
                this.$('svg').html('<text x="50%" y="50%" alignment-baseline="middle" text-anchor="middle">No lineage data found</text>');
                if (this.actionCallBack) {
                    this.actionCallBack();
                }
            },
            hideCheckForProcess: function() {
                this.$('.hideProcessContainer').hide();
            },
            generateData: function(relations, guidEntityMap) {
                var that = this;

                function isProcess(typeName) {
                    var entityDef = that.entityDefCollection.fullCollection.find({ name: typeName });
                    return _.contains(Utils.getNestedSuperTypes({ data: entityDef.toJSON(), collection: that.entityDefCollection }), "Process")
                }

                function isDeleted(status) {
                    return Enums.entityStateReadOnly[status];
                }

                function isNodeToBeUpdated(node) {
                    if (that.filterObj.isProcessHideCheck) {
                        return isProcess(node.typeName);
                    } else if (that.filterObj.isDeletedEntityHideCheck) {
                        return isDeleted(node.status);
                    }
                }

                function getServiceType(typeName) {
                    var serviceType = null;
                    if (typeName) {
                        var entityDef = that.entityDefCollection.fullCollection.find({ name: typeName });
                        if (entityDef) {
                            serviceType = entityDef.get("serviceType") || null;
                        }
                    }
                    return serviceType;
                }

                function makeNodeObj(relationObj) {
                    var obj = {};
                    obj['shape'] = "img";
                    obj['typeName'] = relationObj.typeName
                    obj['label'] = relationObj.displayText.trunc(18);
                    obj['toolTipLabel'] = relationObj.displayText;
                    obj['id'] = relationObj.guid;
                    obj['isLineage'] = true;
                    obj['queryText'] = relationObj.queryText;
                    obj['serviceType'] = getServiceType(relationObj.typeName);
                    if (relationObj.status) {
                        obj['status'] = relationObj.status;
                    }
                    if (that.filterObj.isProcessHideCheck) {
                        obj['isProcess'] = relationObj.isProcess;
                    } else {
                        obj['isProcess'] = isProcess(relationObj.typeName);
                    }

                    return obj;
                }

                var newRelations = [],
                    finalRelations = relations,
                    isHideFilterOn = this.filterObj.isProcessHideCheck || this.filterObj.isDeletedEntityHideCheck;
                if (isHideFilterOn) {
                    _.each(relations, function(obj, index, relationArr) {
                        var toNodeToBeUpdated = isNodeToBeUpdated(guidEntityMap[obj.toEntityId]);
                        var fromNodeToBeUpdated = isNodeToBeUpdated(guidEntityMap[obj.fromEntityId]);
                        if (toNodeToBeUpdated) {
                            if (that.filterObj.isProcessHideCheck) {
                                //We have already checked entity is process or not inside isNodeToBeUpdated();
                                guidEntityMap[obj.toEntityId]["isProcess"] = true;
                            }
                            _.filter(relationArr, function(flrObj) {
                                if (flrObj.fromEntityId === obj.toEntityId) {
                                    newRelations.push({
                                        fromEntityId: obj.fromEntityId,
                                        toEntityId: flrObj.toEntityId
                                    });
                                }
                            })
                        } else if (fromNodeToBeUpdated) {
                            if (that.filterObj.isProcessHideCheck) {
                                //We have already checked entity is process or not inside isNodeToBeUpdated();
                                guidEntityMap[obj.fromEntityId]["isProcess"] = true;
                            }

                            _.filter(relationArr, function(flrObj) {
                                if (flrObj.toEntityId === obj.fromEntityId) {
                                    newRelations.push({
                                        fromEntityId: flrObj.fromEntityId,
                                        toEntityId: obj.toEntityId
                                    });
                                }
                            })
                        } else {
                            newRelations.push(obj);
                        }
                    });
                    finalRelations = newRelations;
                }

                _.each(finalRelations, function(obj, index) {
                    if (!that.fromToObj[obj.fromEntityId]) {
                        that.fromToObj[obj.fromEntityId] = makeNodeObj(guidEntityMap[obj.fromEntityId]);
                        that.g.setNode(obj.fromEntityId, that.fromToObj[obj.fromEntityId]);
                    }
                    if (!that.fromToObj[obj.toEntityId]) {
                        that.fromToObj[obj.toEntityId] = makeNodeObj(guidEntityMap[obj.toEntityId]);
                        that.g.setNode(obj.toEntityId, that.fromToObj[obj.toEntityId]);
                    }
                    var styleObj = {
                        fill: 'none',
                        stroke: '#ffb203',
                        width: 3
                    }
                    that.g.setEdge(obj.fromEntityId, obj.toEntityId, { 'arrowhead': "arrowPoint", lineInterpolate: 'basis', "style": "fill:" + styleObj.fill + ";stroke:" + styleObj.stroke + ";stroke-width:" + styleObj.width + "", 'styleObj': styleObj });
                });

                if (this.fromToObj[this.guid]) {
                    this.fromToObj[this.guid]['isLineage'] = false;
                    this.checkForLineageOrImpactFlag(finalRelations, this.guid);
                }
                if (this.asyncFetchCounter == 0) {
                    this.createGraph();
                }
            },
            checkForLineageOrImpactFlag: function(relations, guid) {
                var that = this,
                    nodeFound = _.where(relations, { 'fromEntityId': guid });
                if (nodeFound.length) {
                    _.each(nodeFound, function(node) {
                        if (!node["traversed"]) {
                            node["traversed"] = true;
                            that.fromToObj[node.toEntityId]['isLineage'] = false;
                            var styleObj = {
                                fill: 'none',
                                stroke: '#fb4200',
                                width: 3
                            }
                            that.g.setEdge(node.fromEntityId, node.toEntityId, { 'arrowhead': "arrowPoint", lineInterpolate: 'basis', "style": "fill:" + styleObj.fill + ";stroke:" + styleObj.stroke + ";stroke-width:" + styleObj.width + "", 'styleObj': styleObj });
                            that.checkForLineageOrImpactFlag(relations, node.toEntityId);
                        }
                    });
                }
            },
            toggleInformationSlider: function(options) {
                if (options.open && !this.$('.lineage-edge-details').hasClass("open")) {
                    this.$('.lineage-edge-details').addClass('open');
                } else if (options.close && this.$('.lineage-edge-details').hasClass("open")) {
                    d3.selectAll('circle').attr("stroke", "none");
                    this.$('.lineage-edge-details').removeClass('open');
                }
            },
            setGraphZoomPositionCal: function(argument) {
                var initialScale = 1.6,
                    svgEl = this.$('svg'),
                    scaleEl = this.$('svg').find('>g'),
                    translateValue = [(this.$('svg').width() - this.g.graph().width * initialScale) / 2, (this.$('svg').height() - this.g.graph().height * initialScale) / 2]
                if (_.keys(this.g._nodes).length > 15) {
                    initialScale = 0;
                    this.$('svg').addClass('noScale');
                }
                if (svgEl.parents('.panel.panel-fullscreen').length) {
                    translateValue = [20, 20];
                    if (svgEl.hasClass('noScale')) {
                        if (!scaleEl.hasClass('scaleLinage')) {
                            scaleEl.addClass('scaleLinage');
                            initialScale = 1.6;
                        } else {
                            scaleEl.removeClass('scaleLinage');
                            initialScale = 0;
                        }
                    }
                } else {
                    scaleEl.removeClass('scaleLinage');
                }
                this.zoom.translate(translateValue)
                    .scale(initialScale);
            },
            zoomed: function(that) {
                this.$('svg').find('>g').attr("transform",
                    "translate(" + this.zoom.translate() + ")" +
                    "scale(" + this.zoom.scale() + ")"
                );
            },
            createGraph: function() {
                var that = this,
                    width = this.$('svg').width(),
                    height = this.$('svg').height();
                this.g.nodes().forEach(function(v) {
                    var node = that.g.node(v);
                    // Round the corners of the nodes
                    if (node) {
                        node.rx = node.ry = 5;
                    }
                });
                // Create the renderer
                var render = new dagreD3.render();
                // Add our custom arrow (a hollow-point)
                render.arrows().arrowPoint = function normal(parent, id, edge, type) {
                    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"]);
                };
                render.shapes().img = function circle(parent, bbox, node) {
                    //var r = Math.max(bbox.width, bbox.height) / 2,
                    if (node.id == that.guid) {
                        var currentNode = true
                    }
                    var shapeSvg = parent.append('circle')
                        .attr('fill', 'url(#img_' + node.id + ')')
                        .attr('r', '24px')
                        .attr("class", "nodeImage " + (currentNode ? "currentNode" : (node.isProcess ? "process" : "node")));

                    parent.insert("defs")
                        .append("pattern")
                        .attr("x", "0%")
                        .attr("y", "0%")
                        .attr("patternUnits", "objectBoundingBox")
                        .attr("id", "img_" + node.id)
                        .attr("width", "100%")
                        .attr("height", "100%")
                        .append('image')
                        .attr("xlink:href", function(d) {
                            if (node) {
                                return Utils.getEntityIconPath({ entityData: node });
                            }
                        })
                        .attr("x", currentNode ? "3" : "6")
                        .attr("y", currentNode ? "3" : "4")
                        .attr("width", "40")
                        .attr("height", "40")
                        .on("error", function() {
                            this.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Utils.getEntityIconPath({ entityData: node, errorUrl: this.href.baseVal }));
                        });

                    node.intersect = function(point) {
                        return dagreD3.intersect.circle(node, currentNode ? 24 : 21, point);
                    };
                    return shapeSvg;
                };
                // Set up an SVG group so that we can translate the final graph.
                if (this.$("svg").find('.output').length) {
                    this.$("svg").find('.output').parent('g').remove();
                }
                var svg = this.svg = d3.select(this.$("svg")[0])
                    .attr("viewBox", "0 0 " + width + " " + height)
                    .attr("enable-background", "new 0 0 " + width + " " + height),
                    svgGroup = svg.append("g");
                var zoom = this.zoom = d3.behavior.zoom()
                    .scaleExtent([0.5, 6])
                    .on("zoom", that.zoomed.bind(this));


                function interpolateZoom(translate, scale) {
                    var self = this;
                    return d3.transition().duration(350).tween("zoom", function() {
                        var iTranslate = d3.interpolate(zoom.translate(), translate),
                            iScale = d3.interpolate(zoom.scale(), scale);
                        return function(t) {
                            zoom
                                .scale(iScale(t))
                                .translate(iTranslate(t));
                            that.zoomed();
                        };
                    });
                }

                function zoomClick() {
                    var clicked = d3.event.target,
                        direction = 1,
                        factor = 0.2,
                        target_zoom = 1,
                        center = [that.g.graph().width / 2, that.g.graph().height / 2],
                        extent = zoom.scaleExtent(),
                        translate = zoom.translate(),
                        translate0 = [],
                        l = [],
                        view = { x: translate[0], y: translate[1], k: zoom.scale() };

                    d3.event.preventDefault();
                    direction = (this.id === 'zoom_in') ? 1 : -1;
                    target_zoom = zoom.scale() * (1 + factor * direction);

                    if (target_zoom < extent[0] || target_zoom > extent[1]) {
                        return false;
                    }

                    translate0 = [(center[0] - view.x) / view.k, (center[1] - view.y) / view.k];
                    view.k = target_zoom;
                    l = [translate0[0] * view.k + view.x, translate0[1] * view.k + view.y];

                    view.x += center[0] - l[0];
                    view.y += center[1] - l[1];

                    interpolateZoom([view.x, view.y], view.k);
                }
                d3.selectAll(this.$('.lineageZoomButton')).on('click', zoomClick);
                var tooltip = d3Tip()
                    .attr('class', 'd3-tip')
                    .offset([10, 0])
                    .html(function(d) {
                        var value = that.g.node(d);
                        var htmlStr = "";
                        if (value.id !== that.guid) {
                            htmlStr = "<h5 style='text-align: center;'>" + (value.isLineage ? "Lineage" : "Impact") + "</h5>";
                        }
                        htmlStr += "<h5 class='text-center'><span style='color:#359f89'>" + value.toolTipLabel + "</span></h5> ";
                        if (value.typeName) {
                            htmlStr += "<h5 class='text-center'><span>(" + value.typeName + ")</span></h5> ";
                        }
                        if (value.queryText) {
                            htmlStr += "<h5>Query: <span style='color:#359f89'>" + value.queryText + "</span></h5> ";
                        }
                        return "<div class='tip-inner-scroll'>" + htmlStr + "</div>";
                    });

                svg.call(zoom)
                    .call(tooltip);
                if (platform.name !== "IE") {
                    this.$('.fontLoader').hide();
                }
                render(svgGroup, this.g);
                svg.on("dblclick.zoom", null)
                    .on("wheel.zoom", null);
                //change text postion 
                svgGroup.selectAll("g.nodes g.label")
                    .attr("transform", "translate(2,-35)");
                svgGroup.selectAll("g.nodes g.node")
                    .on('mouseenter', function(d) {
                        that.activeNode = true;
                        var matrix = this.getScreenCTM()
                            .translate(+this.getAttribute("cx"), +this.getAttribute("cy"));
                        that.$('svg').find('.node').removeClass('active');
                        $(this).addClass('active');

                        // Fix
                        var width = $('body').width();
                        var currentELWidth = $(this).offset();
                        var 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';
                        }
                        tooltip.direction(direction).show(d)
                    })
                    .on('dblclick', function(d) {
                        tooltip.hide(d);
                        Utils.setUrl({
                            url: '#!/detailPage/' + d + '?tabActive=lineage',
                            mergeBrowserUrl: false,
                            trigger: true
                        });
                    }).on('mouseleave', function(d) {
                        that.activeNode = false;
                        var nodeEL = this;
                        setTimeout(function(argument) {
                            if (!(that.activeTip || that.activeNode)) {
                                $(nodeEL).removeClass('active');
                                tooltip.hide(d);
                            }
                        }, 400)
                    });
                svgGroup.selectAll("g.edgePath path.path").on('click', function(d) {
                    var data = { obj: _.find(that.lineageData.relations, { "fromEntityId": d.v, "toEntityId": d.w }) },
                        relationshipId = data.obj.relationshipId;
                    require(['views/graph/PropagationPropertyModal'], function(PropagationPropertyModal) {
                        var view = new PropagationPropertyModal({
                            edgeInfo: data,
                            relationshipId: relationshipId,
                            lineageData: that.lineageData,
                            apiGuid: that.apiGuid,
                            detailPageFetchCollection: that.fetchCollection
                        });
                    });
                })
                $('body').on('mouseover', '.d3-tip', function(el) {
                    that.activeTip = true;
                });
                $('body').on('mouseleave', '.d3-tip', function(el) {
                    that.activeTip = false;
                    that.$('svg').find('.node').removeClass('active');
                    tooltip.hide();
                });

                // Center the graph
                this.setGraphZoomPositionCal();
                zoom.event(svg);
                //svg.attr('height', this.g.graph().height * initialScale + 40);
                if (platform.name === "IE") {
                    this.IEGraphRenderDone = 0;
                    this.$('svg .edgePath').each(function(argument) {
                        var childNode = $(this).find('marker');
                        $(this).find('marker').remove();
                        var eleRef = this;
                        ++that.IEGraphRenderDone;
                        setTimeout(function(argument) {
                            $(eleRef).find('defs').append(childNode);
                            --that.IEGraphRenderDone;
                            if (that.IEGraphRenderDone === 0) {
                                this.$('.fontLoader').hide();
                                this.$('svg').fadeTo(1000, 1)
                            }
                        }, 1000);
                    });
                }
            }
        });
    return LineageLayoutView;
});