/**
 * 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/Globals'
], function(require, Backbone, LineageLayoutViewtmpl, VLineageList, VEntity, Utils, dagreD3, d3Tip, Globals) {
    'use strict';

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

            template: LineageLayoutViewtmpl,

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

            /** ui selector cache */
            ui: {
                graph: ".graph"
            },

            /** ui events hash */
            events: function() {
                var events = {};
                return events;
            },

            /**
             * intialize a new LineageLayoutView Layout
             * @constructs
             */
            initialize: function(options) {
                _.extend(this, _.pick(options, 'globalVent', 'guid'));
                this.inputCollection = new VLineageList();
                this.outputCollection = new VLineageList();
                this.entityModel = new VEntity();
                this.inputCollection.url = "/api/atlas/lineage/" + this.guid + "/inputs/graph";
                this.outputCollection.url = "/api/atlas/lineage/" + this.guid + "/outputs/graph";
                this.bindEvents();
                this.fetchGraphData();
                this.data = {};
                this.fetchList = 0;
            },
            bindEvents: function() {
                this.listenTo(this.inputCollection, 'reset', function() {
                    this.generateData(this.inputCollection, 'input');
                    this.outputCollection.fetch({ reset: true });
                }, this);
                this.listenTo(this.outputCollection, 'reset', function() {
                    this.generateData(this.outputCollection, 'output');
                    this.outputState = true;
                }, this);
                this.listenTo(this.outputCollection, 'error', function() {
                    this.addNoDataMessage();
                }, this);
                this.listenTo(this.inputCollection, 'error', function() {
                    this.addNoDataMessage();
                }, this);
            },
            onRender: function() {
                this.$('.fontLoader').show();
                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 {};
                    });
            },
            fetchGraphData: function() {
                this.inputCollection.fetch({ reset: true });
            },
            addNoDataMessage: function() {
                //this.$('svg').height('100');
                this.$('svg').html('<text x="' + (this.$('svg').width() - 150) / 2 + '" y="' + this.$('svg').height() / 2 + '" fill="black">No lineage data found</text>');
                this.$('.fontLoader').hide();
            },
            generateData: function(collection, type) {
                var that = this;

                function addValueInObject(data) {
                    var obj = {};
                    if (data && data.definition) {
                        if (data.definition.values) {
                            var values = data.definition.values;
                            obj['label'] = values.name.trunc(20);
                            obj['toolTiplabel'] = values.name;
                            obj['id'] = data.GUID;
                            if (values.queryText) {
                                obj['queryText'] = values.queryText;
                            }
                            if (data.definition.id && data.definition.id.state) {
                                obj['state'] = data.definition.id.state;
                            }
                        }
                    } else {
                        obj['label'] = ""
                        obj['toolTiplabel'] = "";
                    }
                    obj['shape'] = "img";
                    obj['class'] = "type-TOP";
                    if (data.GUID) {
                        that.g.setNode(data.GUID, obj);
                    } else {
                        if (data && data.definition) {
                            if (_.isString(data.definition.id)) {
                                that.g.setNode(data.definition.id, obj);
                            } else if (_.isString(data.definition.id.id)) {
                                that.g.setNode(data.definition.id.id, obj);
                            }
                        }
                    }
                    --that.fetchList;
                    if (that.fetchList <= 0) {
                        if (that.edgesAndvertices) {
                            that.createGraph(that.edgesAndvertices, that.startingPoint);
                        } else if (this.outputState && !that.edgesAndvertices) {
                            that.addNoDataMessage();
                        }
                    }
                }

                function fetchLoadProcess(id) {
                    ++that.fetchList;
                    that.entityModel.getEntity(id, {
                        success: function(data) {
                            addValueInObject(data);
                        },
                        error: function(error, data, status) {},
                        complete: function() {}
                    });
                }

                function makeNode(c) {
                    var edges = c.edges,
                        vertices = c.vertices,
                        allKeys = [];
                    _.each(c.edges, function(val, key, obj) {
                        allKeys.push(key);
                        _.each(val, function(val1, key1, obj1) {
                            allKeys.push(val1);
                        });
                    });
                    var uniquNode = _.uniq(allKeys);
                    _.each(uniquNode, function(val, key) {
                        var obj = {};
                        if (vertices[val] && vertices[val].values) {
                            obj['label'] = vertices[val].values.name.trunc(20);
                            obj['toolTiplabel'] = vertices[val].values.name;
                            obj['id'] = val;
                            obj['class'] = "type-TOP";
                            obj['shape'] = "img";
                            obj['typeName'] = vertices[val].values.vertexId.values.typeName;
                            if (vertices[val].values.state) {
                                obj['state'] = vertices[val].values.state;
                            } else if (vertices[val].values.vertexId.values.state) {
                                obj['state'] = vertices[val].values.vertexId.values.state;
                            }
                            if (val && obj) {
                                that.g.setNode(val, obj);
                            }
                        } else {
                            fetchLoadProcess(val);
                        }
                    });
                }
                _.each(collection.models, function(values) {
                    var valuObj = values.get('values');
                    that.startingPoint = [];
                    if (!_.isEmpty(valuObj.edges)) {
                        if (type == "input") {
                            that.edgesAndvertices = {
                                edges: {},
                                vertices: valuObj.vertices
                            };
                            _.each(valuObj.edges, function(val, key, obj) {
                                _.each(val, function(val1, key1, obj1) {
                                    if (!obj[val1]) {
                                        that.startingPoint.push(val1);
                                    }
                                    if (that.edgesAndvertices.edges[val1]) {
                                        that.edgesAndvertices.edges[val1].push(key);
                                    } else {
                                        that.edgesAndvertices.edges[val1] = [key];
                                    }
                                });
                            });
                        } else {
                            that.edgesAndvertices = valuObj;
                            that.startingPoint = [that.guid];
                        }
                        makeNode(that.edgesAndvertices);
                    } else {
                        if (type == 'output') {
                            that.outputState = true;
                        }
                    }
                });
                if (this.fetchList <= 0) {
                    if (this.edgesAndvertices) {
                        this.createGraph(that.edgesAndvertices, this.startingPoint);
                    } else if (this.outputState && !this.edgesAndvertices) {
                        this.$('.fontLoader').hide();
                        that.$('svg').height('100');
                        that.$('svg').html('<text x="' + (that.$('svg').width() - 150) / 2 + '" y="' + that.$('svg').height() / 2 + '" fill="black">No lineage data found</text>');
                    }
                }
            },
            createGraph: function(edgesAndvertices, startingPoint) {
                var that = this,
                    lastVal = "";
                _.each(startingPoint, function(val, key, obj) {
                    _.each(edgesAndvertices.edges[val], function(val1) {
                        if (val && val1) {
                            that.g.setEdge(val, val1, { 'arrowhead': "arrowPoint", lineInterpolate: 'basis' });
                        }
                        createRemaningEdge(edgesAndvertices.edges, val1);
                    });
                });

                function createRemaningEdge(obj, starting) {
                    if (obj[starting] && obj[starting].length) {
                        _.each(obj[starting], function(val, key) {
                            if (starting && val) {
                                that.g.setEdge(starting, val, { 'arrowhead': "arrowPoint", lineInterpolate: 'basis' });
                            }
                            createRemaningEdge(obj, val);
                        });
                    }
                }

                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;
                    }
                });
                if (this.outputState) {
                    // 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", 9)
                            .attr("refY", 5)
                            .attr("markerUnits", "strokeWidth")
                            .attr("markerWidth", 10)
                            .attr("markerHeight", 8)
                            .attr("orient", "auto");

                        var path = marker.append("path")
                            .attr("d", "M 0 0 L 10 5 L 0 10 z")
                            .style("stroke-width", 1)
                            .style("stroke-dasharray", "1,0")
                            .style("fill", "#cccccc")
                            .style("stroke", "#cccccc");
                        dagreD3.util.applyStyle(path, edge[type + "Style"]);
                    };
                    render.shapes().img = function circle(parent, bbox, node) {
                        //var r = Math.max(bbox.width, bbox.height) / 2,
                        var shapeSvg = parent.insert("image")
                            .attr("class", "nodeImage")
                            .attr("xlink:href", function(d) {
                                if (node) {
                                    if (node.typeName) {
                                        if (Globals.entityStateReadOnly[node.state]) {
                                            return '../img/icon-table-delete.png';
                                        } else if (node.id == that.guid) {
                                            return '../img/icon-table-active.png';
                                        } else {
                                            return '../img/icon-table.png';
                                        }
                                    } else {
                                        if (Globals.entityStateReadOnly[node.state]) {
                                            return '../img/icon-gear-delete.png';
                                        } else if (node.id == that.guid) {
                                            return '../img/icon-gear-active.png';
                                        } else {
                                            return '../img/icon-gear.png';
                                        }
                                    }
                                }
                            }).attr("x", "-12px")
                            .attr("y", "-12px")
                            .attr("width", "24px")
                            .attr("height", "24px");
                        /*shapeSvg = parent.insert("circle", ":first-child")
                            .attr("x", 35)
                            .attr("y", 35)
                            .attr("r", 20);*/
                        node.intersect = function(point) {
                            //return dagreD3.intersect.circle(node, points, point);
                            return dagreD3.intersect.circle(node, 13, point);
                        };
                        return shapeSvg;
                    };
                    // Set up an SVG group so that we can translate the final graph.
                    var svg = d3.select(this.$("svg")[0]),
                        svgGroup = svg.append("g");
                    var zoom = d3.behavior.zoom()
                        .scaleExtent([0.5, 6])
                        .on("zoom", zoomed);

                    function zoomed() {
                        svgGroup.attr("transform",
                            "translate(" + zoom.translate() + ")" +
                            "scale(" + zoom.scale() + ")"
                        );
                    }

                    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));
                                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('button.zoomButton').on('click', zoomClick);
                    var tooltip = d3Tip()
                        .attr('class', 'd3-tip')
                        .html(function(d) {
                            var value = that.g.node(d);
                            var htmlStr = "<h5>Name: <span style='color:#359f89'>" + value.toolTiplabel + "</span></h5> ";
                            if (value.queryText) {
                                htmlStr += "<h5>Query: <span style='color:#359f89'>" + value.queryText + "</span></h5> ";
                            }
                            return htmlStr;
                        });
                    svg.call(zoom)
                        .call(tooltip);
                    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,-30)");

                    svgGroup.selectAll("g.nodes image")
                        .on('mouseover', function(d) {
                            tooltip.show(d);
                        })
                        .on('dblclick', function(d) {
                            tooltip.hide(d);
                            //var urlForTab = window.location.hash.split('/')[1];

                            Utils.setUrl({
                                url: '#!/detailPage/' + d,
                                mergeBrowserUrl: false,
                                trigger: true
                            });
                        })
                        .on('mouseout', function(d) {
                            tooltip.hide(d);
                        });
                    // Center the graph
                    var initialScale = 1.2;
                    zoom.translate([(this.$('svg').width() - this.g.graph().width * initialScale) / 2, (this.$('svg').height() - this.g.graph().height * initialScale) / 2])
                        .scale(initialScale)
                        .event(svg);
                    //svg.attr('height', this.g.graph().height * initialScale + 40);
                }
            }
        });
    return LineageLayoutView;
});