import ko from 'knockout';
import $ from 'jquery';
import _ from 'underscore';
import * as d3 from 'd3';
import arches from 'arches';


ko.bindingHandlers.relatedResourcesGraph = {
    init: function(element, valueAccessor) {
        var modelMap = arches.resources.reduce(function(a, v) {
            a[v.graphid] = v;
            return a;
        }, {});
        var options = ko.unwrap(valueAccessor());
        var subscriptions = options.subscriptions;
        var nodeSelection = options.nodeSelection;
        var selectedState = ko.observable(false);
        var $el = $(element);
        var width = $el.parent().width() || 400;
        var height = $el.parent().height() || 400;
        var newNodeId = 0;
        var nodeMap = {};
        var linkMap = {};
        var data = {
            nodes: [],
            links: []
        };
        var texts;
        var selectedNode;

        var simulation = d3.forceSimulation(data.nodes)
            .force("link", d3.forceLink(data.links))
            .force("charge", d3.forceCollide().radius(100))
            .force("radial", d3.forceRadial(300, width/2, height/2))
            .force("center", d3.forceCenter(width / 2, height / 2))
            .alpha(0.01);
            
        var nodeList = options.nodeList;
        var currentResource = options.currentResource;

        var selectNode = function(d) {
            nodesElement.selectAll("circle")
                .attr("class", function(d1) {
                    var className = 'node-' + (d.isRoot ? 'current' : 'ancestor');
                    if (d1 === d) {
                        className += '-selected';
                    } else if (_.has(linkMap, d1.id + '_' + d.id) || _.has(linkMap, d.id + '_' + d1.id)) {
                        className += '-neighbor';
                    }
                    return className;
                });
            linksElement.selectAll("line")
                .attr('class', function(l) {
                    return (l.source === d || l.target === d) ? 'linkMouseover' : 'link';
                });
            nodeSelection([d]);
            updateNodeInfo(d);
        };

        var clearHover = function(d) {
            linksElement.selectAll("line")
                .attr('class', function(l) {
                    return 'link';
                });
            nodesElement.selectAll("circle").attr("class", function(d1) {
                var className = 'node-' + (d1.isRoot ? 'current' : 'ancestor');
                if (d1.selected()) {
                    className += '-selected';
                }
                return className;
            });
        };

        var hoverNode = function(d) {
            nodesElement.selectAll("circle")
                .attr("class", function(d1) {
                    var className = 'node-' + (d.isRoot ? 'current' : 'ancestor');
                    if (d1 === d) {
                        className += d1.selected() ? '-selected' : '-over';
                        if (selectedState() === false) {
                            nodeSelection([d1]);
                        }
                    } else if (_.has(linkMap, d1.id + '_' + d.id) || _.has(linkMap, d.id + '_' + d1.id)) {
                        if (d1.selected() === false) {
                            className += '-neighbor';
                        } else {
                            className += '-selected';
                        }
                    } else if (d1.selected()) {
                        className += '-selected';
                    }
                    return className;
                });
            linksElement.selectAll("line")
                .attr('class', function(l) {
                    return (l.source === d || l.target === d) ? 'linkMouseover' : 'link';
                });
        };

        var updateSelected = function(item) {
            return function(val) {
                selectedState(val);
                if (val === true) {
                    selectNode(item);
                } else {
                    nodeSelection.removeAll();
                    nodesElement.selectAll("circle")
                        .attr("class", function(d1) {
                            return 'node-' + (d1.isRoot ? 'current' : 'ancestor');
                        });
                }
            };
        };

        var updateHovered = function(item) {
            return function(val) {
                if (val === true) {
                    hoverNode(item);
                } else {
                    clearHover(item);
                    if (selectedState() === false) {
                        nodeSelection.removeAll();
                    }
                }
            };
        };

        var svg = d3.select(element).append("svg:svg")
            .attr("viewBox", [0, 0, width, height])
            .call(d3.zoom()
                .extent([[0, 0], [width, height]])
                .scaleExtent([0.25, 8])
                .on("zoom", function(event) {
                    groupElement.attr("transform", event.transform);
                }));

        var groupElement = svg.append('svg:g');
        var linksElement = groupElement.append('svg:g');
        var nodesElement = groupElement.append('svg:g');

        var update = function() {
            var linkMap = linkMap;

            $(window).trigger("resize");
            simulation.nodes(data.nodes);
            simulation.force("link").links(data.links);
            simulation.alpha(0.01).restart();

            var link = linksElement.selectAll("line")
                .data(data.links)
                .join("line")
                .attr("class", "link")
                .on("mouseover", function(event, d) {
                    var hoveredNodes = [];
                    var linkMap = linkMap;
                    d3.select(this).attr("class", "linkMouseover");
                    nodesElement.selectAll("circle").attr("class", function(d1) {
                        var matrix;
                        var className = 'node-' + (d1.isRoot ? 'current' : 'ancestor');
                        if (d.source === d1 || d.target === d1) {
                            className += d1.selected() ? '-selected' : '-neighbor';
                            d1.relationship = (d.target === d1) ? d.relationshipTarget : d.relationshipSource;
                            d1.relationships = d.all_relationships;
                            matrix = this.getScreenCTM();
                            //transform svg coords to screen coords
                            d1.absX = matrix.a * d1.x + matrix.c * d1.y + matrix.e;
                            d1.absY = matrix.b * d1.x + matrix.d * d1.y + matrix.f;
                            hoveredNodes.push(d1);
                        } else if (d1.selected()) {
                            className += '-selected';
                        }
                        return className;
                    });
                    nodeSelection(hoveredNodes);
                })
                .on("mouseout", function(event, d) {
                    d3.select(this).attr("class", "link");
                    nodesElement.selectAll("circle").attr("class", function(d1) {
                        var className = 'node-' + (d1.isRoot ? 'current' : 'ancestor');
                        if (d1.selected()) {
                            className += '-selected';
                        }
                        return className;
                    });
                    nodeSelection.removeAll();
                });
            link.exit()
                .remove();

            var node = nodesElement.selectAll("circle")
                .data(data.nodes, function(d) {
                    return d.id;
                })
                .join("circle")
                .style('fill', function(d) {
                    return d.color;
                })
                .attr("r", function(d) {
                    return d.isRoot ? 24 : 18;
                })
                .attr("class", function(d) {
                    return 'node-' + (d.isRoot ? 'current' : 'ancestor');
                })
                .on("mouseover", function(event, d) {
                    nodesElement.selectAll("circle")
                        .attr("class", function(d1) {
                            var className = 'node-' + (d.isRoot ? 'current' : 'ancestor');
                            if (d1 === d) {
                                className += d1.selected() ? '-selected' : '-over';
                                _.each(nodeList(), function(n) {
                                    if (n.entityid === d.entityid) {
                                        n.hovered(true);
                                    } else {
                                        n.hovered(false);
                                    }
                                });
                            } else if (_.has(linkMap, d1.id + '_' + d.id) || _.has(linkMap, d.id + '_' + d1.id)) {
                                if (d1.selected() === false) {
                                    className += '-neighbor';
                                } else {
                                    className += '-selected';
                                }
                            } else if (d1.selected() === true) {
                                className += '-selected';
                            }
                            return className;
                        });
                    linksElement.selectAll("line")
                        .attr('class', function(l) {
                            return (l.source === d || l.target === d) ? 'linkMouseover' : 'link';
                        });
                })
                .on('mouseout', function(event, d) {
                    nodesElement.selectAll("circle")
                        .attr("class", function(d1) {
                            var className = 'node-' + (d.isRoot ? 'current' : 'ancestor');
                            if (d1.selected()) {
                                className += '-selected';
                            }
                            _.each(nodeList(), function(n) {
                                n.hovered(false);
                                if (n.relationCount) {
                                    n.loaded(n.relationCount.loaded);
                                    n.total(n.relationCount.total);
                                }
                            });
                            return className;
                        });
                    if (selectedState() === false) {
                        nodeSelection.removeAll();
                    }
                    linksElement.selectAll("line")
                        .attr('class', 'link');
                })
                .on("click", function(event, d) {
                    if (!event.defaultPrevented) {
                        d.loadcount(d.loadcount()+1);
                    }
                    nodesElement.selectAll("circle")
                        .attr("class", function(d1) {
                            var className = 'node-' + (d.isRoot ? 'current' : 'ancestor');
                            if (d1 === d) {
                                _.each(nodeList(), function(n) {
                                    if (n.entityid === d.entityid) {
                                        if (n.selected() === false) {
                                            n.selected(true);
                                            className += '-selected';
                                        } else {
                                            n.selected(false);
                                        }
                                    } else {
                                        n.selected(false);
                                    }
                                });
                            } else if (_.has(linkMap, d1.id + '_' + d.id) || _.has(linkMap, d.id + '_' + d1.id)) {
                                className += '-neighbor';
                            }
                            return className;
                        });
                    linksElement.selectAll("line")
                        .attr('class', function(l) {
                            return (l.source === d || l.target === d) ? 'linkMouseover' : 'link';
                        });
                    updateNodeInfo(d);
                })
                .call(d3.drag()
                    .on("start", dragstarted)
                    .on("drag", dragged)
                    .on("end", dragended)
                );

            function dragstarted(event, d) {
                if (!event.active) { simulation.alphaTarget(0.01).restart(); }
                d.fx = d.x;
                d.fy = d.y;
            }
            
            function dragged(event, d) {
                d.fx = event.x;
                d.fy = event.y;
            }
            
            function dragended(event, d) {
                if (!event.active) { simulation.alphaTarget(0); }
                d.fx = null;
                d.fy = null;
            }    

            if (texts) {
                texts.remove();
            }

            texts = nodesElement.selectAll("text.nodeLabels")
                .data(data.nodes)
                .join("text")
                .attr("class", function(d){
                    return d.isRoot ? 'root-node-label' : 'nodeLabels';
                })
                .attr("dy", ".35em")
                .text(function(d) {
                    return d.name;
                });

            simulation.on("tick", function() {
                link.attr("x1", function(d) {
                    return d.source.x;
                })
                    .attr("y1", function(d) {
                        return d.source.y;
                    })
                    .attr("x2", function(d) {
                        return d.target.x;
                    })
                    .attr("y2", function(d) {
                        return d.target.y;
                    });

                node.attr("cx", function(d) {
                    return d.x;
                })
                    .attr("cy", function(d) {
                        return d.y;
                    })
                    .attr("x", function() {
                        return width / 2;
                    })
                    .attr("y", function() {
                        return height / 2;
                    });
                texts
                    .attr("x", function(d) {
                        return d.x;
                    })
                    .attr("y", function(d) {
                        return d.y;
                    });

            });
        };

        var updateNodeInfo = function(d) {
            var iconEl = $el.find('.resource-type-icon');
            $el.find('.selected-resource-name').html(d.name);
            $el.find('.selected-resource-name').attr('href', arches.urls.reports + d.entityid);
            $el.find('.resource-type-name').html(modelMap[d.entitytypeid].name);
            if (d.relationCount) {
                $el.find('.relation-unloaded').hide();
                $el.find('.relation-count').show();
                $el.find('.relation-load-count').html(d.relationCount.loaded);
                $el.find('.relation-total-count').html(d.relationCount.total);
                if (d.relationCount.loaded === d.relationCount.total) {
                    $el.find('.load-more-relations-link').hide();
                } else {
                    $el.find('.load-more-relations-link').show();
                }
            } else {
                $el.find('.load-more-relations-link').show();
                $el.find('.relation-count').hide();
                $el.find('.relation-unloaded').show();
            }
            iconEl.removeClass();
            iconEl.addClass('resource-type-icon');
            iconEl.addClass(modelMap[d.entitytypeid].icon);
            $el.find('.node_info').show();
            selectedNode = d;
        };

        var getResourceDataForNode = function(d) {
            getResourceData(d.entityid, d.name, d.entitytypeid, function(newData) {
                if (newData.nodes.length > 0 || newData.links.length > 0) {
                    data.nodes = data.nodes.concat(newData.nodes);
                    data.links = data.links.concat(newData.links);
                    update(data);
                }
            }, false);
        };

        var getMoreData = function(item) {
            return function(val) {
                if (val) {
                    getResourceDataForNode(item);
                }
            };
        };

        var getResourceData = function(resourceId, resourceName, resourceTypeId, callback, isRoot) {
            var load = true;
            var start = 0;
            var page = 1;
            var rootNode = nodeMap[resourceId];

            if (rootNode) {
                if (rootNode.relationCount) {
                    load = (rootNode.relationCount.total > rootNode.relationCount.loaded && !rootNode.loading);
                    start = rootNode.relationCount.loaded;
                    page = rootNode.loadcount();
                }
            }
            if (load) {
                if (rootNode) {
                    rootNode.loading = true;
                }

                $.ajax({
                    url: arches.urls.related_resources + resourceId,
                    data: {
                        start: start,
                        page: page > 0 ? page : 1
                    },
                    error: function(e) {
                        // eslint-disable-next-line no-console
                        console.log('request failed', e);
                    },
                    success: function(response) {
                        var links = [];
                        var nodes = [];
                        var rr = response.related_resources;
                        var totalLoaded;
                        if (isRoot) {
                            nodeSelection.removeAll();
                            selectedState(false);
                            rootNode = {
                                id: newNodeId,
                                entityid: resourceId,
                                name: resourceName,
                                description: rr.resource_instance.displaydescription,
                                entitytypeid: resourceTypeId,
                                isRoot: true,
                                relationType: 'Current',
                                graphname: rr.node_config_lookup[rr.resource_instance.graph_id].name,
                                iconclass: rr.node_config_lookup[rr.resource_instance.graph_id].iconclass,
                                color: rr.node_config_lookup[rr.resource_instance.graph_id].fillColor,
                                relationCount: {
                                    total: rr.total.value,
                                    loaded: rr.resource_relationships.length
                                }
                            };
                            nodes.push(rootNode);
                            nodeMap[resourceId] = rootNode;
                            newNodeId += 1;
                        } else if (rootNode.relationCount) {
                            totalLoaded = rootNode.relationCount.loaded + rr.resource_relationships.length;
                            rootNode.relationCount.loaded = totalLoaded <= rr.total.value ? totalLoaded : rr.total.value;
                        } else {
                            rootNode.relationCount = {
                                total: rr.total.value,
                                loaded: rr.resource_relationships.length
                            };
                        }
                        rootNode.loading = false;
                        updateNodeInfo(rootNode);

                        var getRelated = function(relatedResource) {
                            var nodeConfigLookup = rr.node_config_lookup;
                            if (!nodeMap[relatedResource.resourceinstanceid]) {
                                var node = {
                                    id: newNodeId,
                                    entityid: relatedResource.resourceinstanceid,
                                    entitytypeid: relatedResource.graph_id,
                                    name: relatedResource.displayname,
                                    description: relatedResource.displaydescription,
                                    color: nodeConfigLookup[relatedResource.graph_id].fillColor,
                                    iconclass: nodeConfigLookup[relatedResource.graph_id].iconclass,
                                    graphname: nodeConfigLookup[relatedResource.graph_id].name,
                                    isRoot: false,
                                    relationType: 'Ancestor',
                                    relationCount: {
                                        total: relatedResource.total_relations.value,
                                        loaded: 1
                                    }
                                };
                                nodes.push(node);
                                nodeMap[relatedResource.resourceinstanceid] = node;
                                newNodeId += 1;
                            }
                        };
                        _.each(rr.related_resources, getRelated);

                        _.each(rr.resource_relationships, function(resourceRelationships) {
                            var sourceId = nodeMap[resourceRelationships.from_resource];
                            var targetId = nodeMap[resourceRelationships.to_resource];
                            var relationshipSource = resourceRelationships.relationshiptype_label;
                            var relationshipTarget = resourceRelationships.relationshiptype_label;
                            if (resourceRelationships.relationshiptype_label.split('/').length === 2) {
                                relationshipSource = resourceRelationships.relationshiptype_label.split('/')[0].trim();
                                relationshipTarget = resourceRelationships.relationshiptype_label.split('/')[1].trim();
                            }

                            links.push({
                                source: sourceId,
                                target: targetId,
                                relationshipSource: relationshipSource,
                                relationshipTarget: relationshipTarget,
                                weight: 1
                            });

                            if (!_.has(linkMap, [sourceId.id + '_' + targetId.id])) {
                                linkMap[sourceId.id + '_' + targetId.id] = {relationships:[]};
                            }
                            if (!_.has(linkMap, [targetId.id + '_' + sourceId.id])) {
                                linkMap[targetId.id + '_' + sourceId.id] = {relationships:[]};
                            }
                            if (_.contains(linkMap[sourceId.id + '_' + targetId.id]['relationships'], relationshipSource) === false) {
                                linkMap[sourceId.id + '_' + targetId.id]['relationships'].push(relationshipSource);
                            }
                            if (_.contains(linkMap[targetId.id + '_' + sourceId.id]['relationships'], relationshipSource) === false) {
                                linkMap[targetId.id + '_' + sourceId.id]['relationships'].push(relationshipSource);
                            }
                        });

                        links = _.uniq(links, function(item, key, source) {
                            return item.source.id + '_' + item.target.id;
                        });

                        _.each(links, function(l){
                            if (_.has(linkMap, l.source.id + '_' + l.target.id)) {
                                l.all_relationships = linkMap[l.source.id + '_' + l.target.id].relationships;
                            }
                        });

                        nodeList(nodeList().concat(nodes));

                        callback({
                            nodes: nodes,
                            links: links
                        });
                    }
                });
            }
        };

        var setRoot = function(val) {
            if (val.graphid !== undefined) {
                nodeMap = {};
                linkMap = {};
                nodeList([]);
                getResourceData(val.resourceinstanceid, val.displayname, val.graphid, function(newData) {
                    $el.removeClass('loading');
                    data = newData;
                    data.nodes[0].x = width / 2;
                    data.nodes[0].y = height / 2 - 160;
                    update();
                }, true);
            }
        };

        if (currentResource().resourceinstanceid) {
            setRoot(currentResource());
        }

        if (ko.isObservable(currentResource)) {
            var subscription = currentResource.subscribe(setRoot, this);
            if (subscriptions.length > 0) {
                _.each(subscriptions, function(s) {
                    s.dispose();
                });
            }
            subscriptions.push(subscription);
        }

        $(window).on("resize", function() {
            var w = $el.parent().width();
            var h = $el.parent().height();
            svg.attr("width", w);
            svg.attr("height", h);
            svg.attr("viewBox", [0, 0, w, h]);
        }).trigger("resize");


        nodeList.subscribe(function(list) {
            _.each(list, function(item) {
                if (item.selectedSubscription) {
                    item.selectedSubscription.dispose();
                    item.hoveredSubscription.dispose();
                    item.loadcountSubscription.dispose();
                }
                item.selectedSubscription = item.selected.subscribe(updateSelected(item), this);
                item.hoveredSubscription = item.hovered.subscribe(updateHovered(item), this);
                if (item.isRoot && item.loadcount() === 0) {
                    item.loadcount(1);
                }
                item.loadcountSubscription = item.loadcount.subscribe(getMoreData(item), this);

                if (item.relationCount) {
                    item.loaded(item.relationCount.loaded);
                    item.total(item.relationCount.total);
                }
            });
        }, this);

        nodeList([]);
    }
};
ko.bindingHandlers.relatedResourcesGraph.init = ko.bindingHandlers.relatedResourcesGraph.init.bind(ko.bindingHandlers.relatedResourcesGraph);

export default ko.bindingHandlers.relatedResourcesGraph;
