import { event, select, selectAll} from 'd3-selection';
import { values, set, map } from 'd3-collection';
import { drag } from 'd3-drag';
import { zoom } from 'd3-zoom';
import { forceSimulation, forceLink, forceManyBody, forceCenter, forceX, forceY, forceRadial } from 'd3-force';
// import { Wiki, Page } from './wiki.js';
import EventEmitter from 'eventemitter3';
// import { json } from 'd3-fetch';

export class ForceNet {

    constructor (symbols) {
        var width = 600,
            height = 600;

        this.symbols = symbols || {};
        this.events = new EventEmitter();
        this.active_page = null;
        // this.nodes = {};
        this.simulation = forceSimulation()
            //.velocityDecay(0.1)
		    .force("link", forceLink().id(d => d.title).distance(800))
            .force("charge", forceManyBody().distanceMin(800))
            .force("radial", forceRadial(180, width/2, height/2));
            // .force("center", forceCenter(width / 2, height / 2));
        this.svg = null;
        // this.historylinks = {};
    }

    on (message, callback, context) {
        this.events.on(message, callback, context);
    }

    init_svg (svg) {
        this.svg = select(svg || "svg");
        this.zoom = zoom()
            .scaleExtent([1 / 16, 16])
            .on("zoom", () => {
              this.content.attr("transform", event.transform);
              // console.log("transform", event.transform, this.content.attr("transform"));
            });
        this.rect = this.svg.append("rect")
            .attr("width", 1000)
            .attr("height", 1000)
            .style("fill", "none")
            .style("pointer-events", "all")
            .call(this.zoom);
        this.content = this.svg.append("g")
            .attr("id", "content"),
        this.linksg = this.content.append("g")
            .attr("class", "links");
        this.nodesg = this.content.append("g")
            .attr("class", "nodes");
    }

    dragstarted (d) {
        if (!event.active) this.simulation.alphaTarget(0.3).restart();
        d.fx = d.x;
        d.fy = d.y;
    }

    dragged (d) {
        d.fx = event.x;
        d.fy = event.y;
    }

    dragended(d) {
        if (!event.active) this.simulation.alphaTarget(0);
        d.fx = null;
        d.fy = null;
    }

    link_key (a, b) {
        return (a < b) ? ("link_"+a+"_"+b) : ("link_"+b+"_"+a);
    }

    /*
    link_key (p1, p2) {
        return (p1.title < p2.title) ?
            ("link_"+p1.title+"_"+p2.title) :
            ("link_"+p2.title+"_"+p1.title);
    }
    */
    make_link (p1, p2) {
        return (p1.title < p2.title) ?
            {source: p1, target: p2 } :
            {source: p2, target: p1 };        
    }

    register_link (from_page, to_page) {
        var lkey = this.link_key(from_page, to_page);
        if (this.links[lkey] === undefined) {
            this.links[lkey] = this.make_link(from_page, to_page);
        }
    }



    get_symbol (d, def) {
        return "symbols.svg#Main";
        /*
        for (var i=0, l=d.cats.length; i<l; i++) {
            if (this.symbols[d.cats[i]]) {
                return this.symbols[d.cats[i]];
            }
        }
        return this.symbols.default || def;
        */
    }

    update_graph (graph) {
        console.log("UPDATE GRAPH", graph.nodes.length, graph.links.length);
        var link = this.linksg.selectAll("line")
                .data(graph.links, d => { return this.link_key(d.source.title, d.target.title) });
        var link_enter = link.enter()
            .append("line");

        link.exit().each(d => {
            d.source.linked = false;
            d.target.linked = false;
        }).remove();
        
        link_enter.merge(link).each(d => {
                d.source.linked = true;
                d.target.linked = true;
            });

        var node = this.nodesg
            .selectAll("g.page")
            .data(graph.nodes, function (d) { return d.title });
        
        node.exit().remove();

        var that = this;
        var node_enter = node.enter().append("g")
            .attr("class", "page")
            // .attr("class", d=>"page "+this.wiki.get_ns_classname(d.ns))
            .on("click", function(d) {
                that.events.emit("nodeclick", d, this);
                // this.set_active_node(d.title);
            })
            .on("mouseover", function (d) {
                // console.log("mouseover", this);
                select(this).classed("mouse", true);
            })
            .on("mouseout", function (d) {
                // console.log("mouseout", this);
                select(this).classed("mouse", false);
            })
            .call(drag()
                .on("start", this.dragstarted.bind(this))
                .on("drag", this.dragged.bind(this))
                .on("end", this.dragended.bind(this)));

        node_enter.append("use")
            .attr("xlink:href", d => this.get_symbol(d, "default"))
            .attr("class", "testcolor");

            // {
            //     for (var i=0, l=d.cats.length; i<l; i++) {
            //         if (this.symbols[d.cats[i]]) {
            //             return this.symbols[d.cats[i]];
            //         }
            //     }
            //     return this.symbols.default || "default";
            // });

        // node_enter.append("circle")
        //     .attr("r", 6);

        node_enter.append("text")
            .text(d => d.title)
            .attr("x", 10);

        //node_enter.append("title")
        //   .text(function(d) { return d.title; });

        node = node_enter.merge(node);
        link = link_enter.merge(link);

        node.classed("active", d=>d.active);


        this.simulation
            .nodes(graph.nodes)
            .on("tick", ticked);

        this.simulation.force("link")
            .links(graph.links);

        this.simulation.force("radial").radius(d => d.linked ? null : 200);

        function ticked() {
            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; });
            node
                .attr("transform", d => `translate(${d.x},${d.y})`);
        }

        // document.querySelector("#page").style.background = "purple";
        // return;

        this.update_nodes();
        this.update_forces();
        // this.simulation.alphaTarget(0.3).restart();
    }

    update_nodes () {
        var nodes = this.nodesg.selectAll("g.page");
        console.log("update_nodes", nodes.size());
        nodes.classed("active", d=>d.active);
        nodes.classed("active2", d=>d.active2);
        nodes.classed("highlight", d=>d.highlight);
        nodes.sort((a, b) => {
            // console.log("sort", a, b);
            var x = a.active ? 10 : (a.active2 ? 5 : 0),
                y = b.active ? 10 : (b.active2 ? 5 : 0);
            return x - y; 
        });
        var links = this.linksg.selectAll("line");
        links.classed("active2", d=>d.active2);        
        links.classed("history", d=>d.type == "history");        
        links.sort((a, b) => {
            // console.log("sort", a, b);
            var x = a.active2 ? 10 : (a.history ? 5 : 0),
                y = b.active2 ? 10 : (b.history ? 5 : 0);
            return x - y; 
        });
    }

    update_forces () {
        var force = this.simulation.force("link");
        console.log("update_forces:force", force);
        this.simulation.force("link").strength(d => {
            if (d.source.active || d.target.active) {
                return 1;
            } else {
                return 1;
                // same as d3.force's defaultStrength
                // return 0.5 * (1 / Math.min(d.source.count, d.target.count)); 
            }
        });
        this.simulation.alphaTarget(0.3).restart();
    }

    activate_linked_nodes (page, active) {
        // deactivate linked links/nodes
        for (let i=0, l=this.links.length; i<l; i++) {
            let link = this.links[i];
            if (link.source == page || link.target == page) {
                link.active2 = active;
                link.source.active2 = active;
                link.target.active2 = active;
            }
        }        
    }


}

