import { event, select, selectAll} from 'd3-selection';
import { values, set, map } from 'd3-collection';
import { drag } from 'd3-drag';
import { zoom, zoomIdentity, zoomTransform } 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';
import { mobilecheck } from './mobilecheck.js';

// import { ForceNet } from './forcenet.js';

export class Map {

    constructor (opts) {
        var width = 600,
            height = 600;
        this.width = width;
        this.height = height;
        this.zoom_level = opts.zoom || 1.5;
        this.apiurl = opts.apiurl;
        this.categorylabel = opts.categorylabel || "Category";
        this.symbols_src = opts.symbols;
        this.categorydiv = select(opts.categorydiv);

        this.wiki = new Wiki(this.apiurl);

        this.events = new EventEmitter();
        this.active_page = null;
        // this.symbols = symbols;
        // this.net = new ForceNet({});
        // this.net.on("nodeclick", this.nodeclick.bind(this));
        this.svg = null;
        this.init_svg(opts.svg);
        this.historylinks = {};
        this.links = null;
        this.highlight_category = null;
        this.show_history = true;
        this.loaded = false;
        this.active_url = null;
        this.active_page = null;

        this.simulation = forceSimulation()
            //.velocityDecay(0.1)
            .force("link", forceLink().id(d => d.title))
            .force("charge", forceManyBody())
            .force("radial", forceRadial(180, width/2, height/2));
            // .force("center", forceCenter(width / 2, height / 2));

        this.all_links_by_key = {};
    }

    async init () {
        this.symbols = await json(this.symbols_src);
        await this.wiki.init();

        // load categories & set their page symbols
        for (let i=0, l=this.symbols.length; i<l; i++) {
            let sym = this.symbols[i];
            if (sym.key === "default") {
                let page = this.wiki.get_page_by_title("Special:AllPages");
                sym.page = page;
            } else {
                let cat = this.wiki.get_page_by_title(this.categorylabel+":"+sym.key);
                sym.page = cat;
                cat.pages = await cat.get_categorymembers();
                // console.log("got cat pages", cat);
                for (let j=0, jlen=cat.pages.length; j<jlen; j++) {
                    let cp = cat.pages[j];
                    cp.symbol = sym.symbol;
                }
            }
        }

        // create the categories
        let cat = this.categorydiv
            .selectAll("div.cat")
            .data(this.symbols)
            .enter()
            .append("div")
            .attr("class", "cat icon");
        cat.append("span")
            .attr("class", "icon")
            .style("background-image", d => "url("+d.icon+")");
        cat.append("a")
            .attr("class", "label")
            .html(d => d.key === "default" ? "Page" : d.key)
            .attr("href", "#").on("click", d => {
                event.preventDefault();
                this.category_click(d);
            })
        cat.append("span").attr("class", "count").html(d => d.tcount)

        var set_active_url_from_window = () => {
            var title = this.wiki.unescapeTitle(window.location.hash.substring(1)),
                page = this.wiki.get_page_by_title(title),
                url = page.url();
            console.log("wikimap init", page.title, url);
            this.set_active_url(url);            
        };
        
        this.loaded = true;
        if (window.location.hash) {
            set_active_url_from_window();
        } else if (this.active_url) {
            this.set_active_url(this.active_url);
        }

        window.addEventListener("hashchange", e => {
            console.log("hashchange");
            set_active_url_from_window()
        });
        /*
        window.addEventListener("popstate", e => {
            // console.log("popstate", e);
            console.log("popstate: " + document.location + ", state: " + JSON.stringify(e.state));
        })
        */
    }

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

    async json (src) {
        // expose d3's json method
        return await json(src);
    }

    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"));
            })
            .filter(function () {
                // console.log("filter", event);
                if (event.touches && event.touches.length == 1) {
                    return false;
                }
                return true;
            });

        this.rect = this.svg.append("rect")
            .attr("width", 1000)
            .attr("height", 1000)
            .style("fill", "none");
        if (!mobilecheck()) {
            this.rect.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");

            // DEBUGGING
            this.svg.on("click", x => {
                // console.log("svg click", event.target);
                if (event.target == this.svg.node()) {
                    console.log("(debug) BACKGROUND CLICK", this.active_page.x, this.active_page.y);
                    this.centerOnItem(this.active_page);
                }
            })
    }

    dragstarted (d) {
        if (!event.active) this.simulation.alphaTarget(0.3).restart();
        // this.simulation.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;
    }

    /* Links */

    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 };        
    }

    ensure_link (from_page, to_page) {
        var lkey = this.link_key(from_page, to_page),
            ret = this.all_links_by_key[lkey];
        if (ret === undefined) {
            ret = this.make_link(from_page, to_page);
            this.all_links_by_key[lkey] = ret;
        }
        return ret;
    }

    // function called on iframe load
    async set_active_url (url) {
        console.log("wikimap.set_active_url", url);
        this.active_url = url;        
        if (this.loaded) {
            var page = this.wiki.get_page(this.active_url);
            if (page) {
                // console.log("calling replaceState", page.title);
                window.history.replaceState (null, null, "#"+this.wiki.escapeTitle(page.title));
                // window.location.hash = this.wiki.escapeTitle(page.title);
                this.set_active_page(page);
            } else {
                console.log("wikimap: set_active_url: NO PAGE FOR", url);
            }
        }
    }

    linked_nodes_set_active (page, active) {
        // deactivate linked links/nodes
        for (var key in this.all_links_by_key) {
            if (this.all_links_by_key.hasOwnProperty(key)) {
                var link = this.all_links_by_key[key];
                if (link.source == page || link.target == page) {
                    link.active2 = active;
                    link.source.active2 = active;
                    link.target.active2 = active;
                }
            }
        }
    }

    clear_highlight_category () {
        if (this.highlight_category) {
            // cleanup old pages
            this.highlight_category.highlight = false;
            this.highlight_category.pages.forEach(d => d.highlight = false);
        }
        if (this.active_page == this.highlight_category) {
            this.active_page = null;
        }
        this.highlight_category = null;
        this.categorydiv
            .selectAll("div.cat")
            .classed("highlight", d=> d.page ? d.page.highlight : false);
        var data = {nodes: this.get_nodes(), links: values(this.all_links_by_key)};
        this.update_node_counts()
        this.update_graph(data);
    }

    get_nodes () {
        var nodes = this.wiki.get_nodes();
        nodes = nodes.filter(d => d.linked || d.active || d.active2 || d.highlight);
        return nodes;
    }

    async set_active_page (page) {
        console.log("wikimap: set_active_page:", page);
        if (page === this.active_page) {
            // console.log("page is already the active page", page, this.active_page);
            return;
        }
        // window.location.hash = this.wiki.escapeTitle(page.title);
        // window.history.pushState ({pagetitle: page.title}, page.title, "/m/"+this.wiki.escapeTitle(page.title));
        // cleanup old
        if (this.active_page) {
            this.active_page.fx = undefined;
            this.active_page.fy = undefined;
            this.active_page.active = false;
            // deactivate linked links/nodes
            this.linked_nodes_set_active(this.active_page, false);
            // ENSURE (HISTORY) LINK TO PREVIOUS NODE AND CURRENT
            //var link = this.ensure_link(this.active_page, page);
            // link.visited = true;
        }

        if (page.ns == 14) {
            this.active_page = page;
            if (!page.pages) {
                page.pages = await page.get_categorymembers();
            }
            if (this.highlight_category) {
                // cleanup old pages
                this.highlight_category.highlight = false;
                this.highlight_category.pages.forEach(d => d.highlight = false);
            }
            this.highlight_category = page;
            this.highlight_category.highlight = true;
            this.highlight_category.pages.forEach(d => d.highlight = true);
            // this.update_nodes();

            this.categorydiv
                .selectAll("div.cat")
                .classed("highlight", d=> d.page ? d.page.highlight : false);
            // repetition of below... (could be improved)
            // this.events.emit("page", this.active_page);
            // var data = {nodes: this.wiki.get_nodes(), links: values(this.all_links_by_key)};
            // this.update_node_counts()
            // this.update_graph(data);

            // return;
        } else if (page.ns !== 0) {
            console.log("SPECIAL PAGE", page);

        } else {
            // LOAD/ENSURE PAGE LINKS
            var links_out = await page.get_links();
            links_out = links_out.filter(p => (!p.redirect && p.ns == 0));
            // console.log("links_out", links_out);
            var links_in = await page.get_linkshere();
            links_in = links_in.filter(p => (!p.redirect && p.ns == 0));
            // console.log("links_in", links_in);
            links_out.forEach(p => {
                this.ensure_link(page, p).wiki = true;
            });
            links_in.forEach(p => {
                this.ensure_link(p, page).wiki = true;
            });

        }

        this.active_page = page;
        // console.log("fixing active page", this.active_page);
        if (this.active_page.x === undefined) {
            this.active_page.x = this.width/2;
            this.active_page.y = this.height/2;
        } 
        this.active_page.fx = this.active_page.x;
        this.active_page.fy = this.active_page.y;
        this.active_page.active = true;
        this.linked_nodes_set_active(this.active_page, true);
        
        //setTimeout(() => {
        this.centerOnItem(page, 1000);
        //}, 1000);

        this.events.emit("page", this.active_page);
        var data = {nodes: this.get_nodes(), links: values(this.all_links_by_key)};
        this.update_node_counts()
        this.update_graph(data);
        // this.update_nodes();
        // this.update_forces();
    }

    update_node_counts () {
        var nodes = this.wiki.get_nodes();
        for (let i=0, len=nodes.length; i<len; i++) {
            nodes[i].count = 0;
        }
        for (var key in this.all_links_by_key) {
            if (this.all_links_by_key.hasOwnProperty(key)) {
                var link = this.all_links_by_key[key];
                link.source.count += 1;
                link.target.count += 1;
            }
        }
    }

    category_click (d) {
        console.log("category click", d);
        if (d.page) {
            if (this.highlight_category == d.page) {
                this.clear_highlight_category();
            } else {
                this.set_active_page(d.page);
            }
        }
    }

    set_show_history (value) {
        console.log("wikimap.show_history", value);
        if (this.show_history !== value) {
            this.show_history = value;
            if (this.show_history) {
                let graph = {};
                graph.nodes = this.nodes;
                graph.links = this.links.slice();
                for (var key in this.historylinks) {
                    graph.links.push(this.historylinks[key])
                }
                this.update_graph(graph);
            } else {
                let graph = {};
                graph.nodes = this.nodes;
                graph.links = this.links;
                this.update_graph(graph);
            }
        }
    }

    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", d => {
                // that.events.emit("nodeclick", d, this);
                this.set_active_page(d);
                // this.set_active_node(d.title);
            })
            .on("mouseover", function (d) {
                // console.log("mouseover", this);
                // d.mouse = true;
                select(this).classed("mouse", true);
                // that.update_nodes();
            })
            .on("mouseout", function (d) {
                // console.log("mouseout", this);
                // d.mouse = false;
                select(this).classed("mouse", false);
                // that.update_nodes();
            })
            .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 => d.symbol || "symbols.svg#Main")
            .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 || d.highlight) ? 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("mouse", d=>d.mouse);
        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.mouse ? 10 : (a.active ? 8 : (a.active2 ? 5 : 0)),
                y = b.mouse ? 10 : (b.active ? 8 : (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 {
                // same as d3.force's defaultStrength
                return 0.5 * (1 / Math.min(d.source.count, d.target.count)); 
            }
        });
        // this.simulation.alphaTarget(0.3).restart();
        this.simulation.alpha(0.5).restart();
    }

    centerOnItem(item, duration) {
        var bounds = this.svg.node().getBoundingClientRect();
        var curt = zoomTransform(this.rect.node());
        // console.log("centerOnItem", this.zoom_level, "item", item);
        var zoom_level = this.zoom_level ? this.zoom_level : curt.k;
        var ITEM_SIZE = 36;
        if (item && item.x !== undefined) {
            this.zoom.translateTo(duration ? this.rect.transition().duration(duration) : this.rect, item.x, item.y);
            // this.zoom.translateTo(this.rect, item.x, item.y);
            /*
            var transform = function () {
                return zoomIdentity
                    .translate(bounds.width / 2, bounds.height / 2)
                    .scale(zoom_level)
                    .translate(-item.x, -item.y);
            };
            if (duration) {
                this.rect.transition().duration(duration).call(this.zoom.transform, transform);
            } else {    
                this.rect.call(this.zoom.transform, transform);
            }
            */
        } else {
            console.log("NO ITEM");
            var transform = function () {
                return zoomIdentity
                    .scale(1);
            };
            this.rect.call(this.zoom.transform, transform);
        }
    }

    do_zoom (f, transition) {
        this.rect.call(this.zoom.scaleBy, f);
    }

    zoom_in () {
        this.do_zoom(1.25);
    }

    zoom_out () {
        this.do_zoom(1/1.25);
    }
}

