import * as d3 from 'd3';
import ELK from 'elkjs';


import { Module } from './layout';
import { globalLookup, globalSetting } from '../global';
import { PortRender } from './port';
import { InstantiationRender } from './instantiation';
import { CellRender } from './cell';
import { ConnectionRender } from './connection';
import { WireRender } from './wire';
import { pinkLog, redLog } from '../utils';
import { treeviewData } from '@/components/treeview/tree';
import { dotConnect } from './yosys';
import { CrossDotRender } from './cross-dot';
import { RangeTreeMap } from '../algorithm/range-tree';
import { ConstantRender } from './constant';

export class NetlistRender {
    /**
     * 
     * @param {YosysRawNet} rawNet 
     * @param {number} renderHeight
     * @param {number} renderWidth
     */
    constructor() {
        this.defaultLayoutOptions = {
            // ...
            'org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers': 35,
            // node 之间的最小间距
            'elk.spacing.nodeNode': 35,
            // layered 算法布局，尽可能让全体布局从左向右
            'elk.algorithm': 'layered',
            // layered 算法的风格
            'elk.layered.layering.strategy': 'NETWORK_SIMPLEX',
            // ...
            'elk.layered.considerModelOrder.longEdgeStrategy': 'DUMMY_NODE_UNDER',
            // edge 的生成采用正交路由算法
            'elk.edgeRouting': 'ORTHOGONAL',
            // 指定 layered 算法的方向为从左到右
            'elk.direction': 'RIGHT',
            // 激活 node 分区功能，可以通过 partitioning.partition 赋予 id 的方式给 node 进行分组
            'elk.partitioning.activate': true,
            // 边之间的距离
            'elk.spacing.edgeEdge': 25
        }
        /**
         * @type {ElkGraph}
         */
        this.elkGraph = {
            id: 'root',
            children: [],
            edges: [],
            layoutOptions: this.defaultLayoutOptions
        };

        /**
         * @type {import('elkjs').ELK}
         */
        this.elk = new ELK();

        /**
         * @type {Map<string, Module>}
         */
        this.nameToModule = new Map();

        this.maxX = -1;
        this.minX = 1e12;
        this.maxY = -1;
        this.minY = 1e12;

        /**
         * @type {number} 一开始绘制完成后整体的横向偏移量（默认一开始从左上角开始画，不居中）
         */
        this.startOffsetX = 0;

        /**
         * @type {number} 一开始绘制完成后整体的纵向偏移量（默认一开始从左上角开始画，不居中）
         */
        this.startOffsetY = 0;

        /**
         * @description id 到管理数据项的映射
         * 
         * - key: id, 和 layout.js 中的赋值 id 一致，全局唯一
         * - value: 每一个 id 对应的管理者，这里存储着数据视图和渲染视图，查询与操作DOM一体。一个 ID 对应的真实渲染体可能不只一个
         * （比如对于 wire，一个 id 对应的 wire 可以是连续的折现，但是这些折现对应的渲染体是复数个的）
         * @type {Map<string, BasicD3ManagmentItem[]>}
         */
        this.id2manager = new Map();
    }

    /**
     * @description 加载 yosys 格式的 json
     * @param {YosysRawNet} rawNet 
     */
    load(rawNet) {
        /**
         * @type {YosysRawNet}
         */
        this.rawNet = rawNet;
        this.topModuleName = globalLookup.topModuleName;
        pinkLog(`当前模块 ${this.topModuleName}`);
        
        /**
         * @type {Module}
         */
        let topModule = undefined;

        // 构造模块树
        for (const [moduleName, rawModule] of Object.entries(rawNet.modules)) {
            const module = new Module(moduleName, rawModule);
            this.nameToModule.set(moduleName, module);

            if (moduleName === this.topModuleName) {
                // 构造符合 elk 格式的节点数据
                topModule = module;
                const portNodes = module.makeNetsElkNodes();
                const cellNodes = module.makeCellsElkNodes();
                const [constantNodes, connectionEdges] = module.makeConnectionElkNodes();

                // 挂载到渲染图中
                this.elkGraph.children.push(...portNodes);
                this.elkGraph.children.push(...cellNodes);
                this.elkGraph.children.push(...constantNodes);
                this.elkGraph.edges.push(...connectionEdges);
            }
        }

        if (topModule === undefined) {
            redLog(`当前 netlist 中没有找到名为 ${this.topModuleName} 的顶层模块！ Digital IDE 无法构建它的 treeview 视图！`);
            return;
        }
        
        // 根据 topModule + this.nameToModule 构建出用于渲染右侧列表的数据结构
        const nameToModule = this.nameToModule;

        // 记录当前需要不断递归解析成渲染视图的 module 的 name
        // 从将每一个 module 的 tree 中关于 Cell 的部分完成指向（判断这个 Cell 是不是 例化模块，并找到它们对应的 Module 对象）
        const moduleStack = [topModule.name];

        while (moduleStack.length > 0) {
            const moduleName = moduleStack.pop();
            const currentModule = nameToModule.get(moduleName);
            // 需要展示的数据：port, cell, (other netnames)
            const view = currentModule.view;
            for (const cellName of view.nameToCell.keys()) {
                const cell = view.nameToCell.get(cellName);
                const solveInstanceTypes = new Set();
                if (nameToModule.has(cell.type)) {
                    cell.isInstantiation = true;
                    const instModule = nameToModule.get(cell.type);
                    cell.belongModuleView = instModule.view;
                    solveInstanceTypes.add(cell.type);
                }
                for (const instanceBelongModuleName of solveInstanceTypes) {
                    moduleStack.push(instanceBelongModuleName);
                }
            }
        }

        // 将默认的顶层模块的 view 加入响应数组中，响应地去渲染右侧的 view
        const rootView = topModule.view;        
        treeviewData.modules.push(rootView);
    }

    /**
     * @description 根据信息创建布局对象
     * @returns {Promise<ElkNode>}
     */
    async createLayout() {
        const graph = this.elkGraph;
        const start = performance.now();
        const layoutGraph = await this.elk.layout(graph);
        const timecost = (performance.now() - start).toFixed(3);

        pinkLog(`布局计算耗时 ${timecost} ms`);

        return layoutGraph;
    }

    /**
     * 
     * @param {ElkNode} computedLayout 
     * @param {string} container 类似于 "#chart"
     * @returns {d3.Selection}
     */
    async render(computedLayout, container) {
        const virtualHeight = computedLayout.height;
        const virtualWidth = computedLayout.width;

        const element = document.querySelector(container);
        this.renderHeight = element.clientHeight;
        this.renderWidth = element.clientWidth;
        this.registerResizeHandler(element);

        // 遍历计算布局进行创建
        const svg = d3.select(container)
            .selectAll('svg')
            .attr('width', this.renderWidth)
            .attr('height', this.renderHeight);

        // 将分组作为一个后续操作的 parent selection
        const g = svg.append('g');

        // debug
        console.log(computedLayout);

        // 生成连接
        await this.renderLine(g, computedLayout);
        
        // 生成实体
        await this.renderEntity(g, computedLayout);

        // svg 挂载为全局注册的 selection
        this.selection = svg;
        this.g = g;

        // 注册平移和缩放
        this.registerRenderTransform(g);

        // 根据最大最小尺寸微调全局方位
        this.adjustLocation(g);
        
        // setTimeout(() => {
        //     // 生成各个 manager 的拖拽上下文
        //     this.createManagerDragContext(computedLayout);
        // }, 1000);
        
        return svg;
    }

    /**
     * @description 绘制实体
     * @param {d3.Selection} parentSelection 
     * @param {import('../jsdoc').ElkNode} computedLayout 
     */
    async renderEntity(parentSelection, computedLayout) {
        // node 可能是如下的几类
        // - module 的 port
        // - 器件（基础器件 & 例化模块）
        // - 器件的 port
        const skinManager = globalLookup.skinManager;

        // 创建各个主要实体的 render
        this.cellRender = new CellRender(parentSelection, this);
        this.portRender = new PortRender(parentSelection, this);
        this.instantiationRender = new InstantiationRender(parentSelection, this);
        this.connectionRender = new ConnectionRender(parentSelection, this);
        this.constantRender = new ConstantRender(parentSelection, this);

        for (const node of computedLayout.children) {
            // 只计算形体的，因为 连接点 非常小，几乎不影响布局
            this.updateMaxMinSize(node.x, node.y, node.width, node.height);

            const skin = skinManager.querySkin(node.renderName);
            if (skin) {
                // 具有 skin 的器件
                const element = skin.meta.svgDoc.documentElement.cloneNode(true);
                this.cellRender.addAsD3DataItem(node, element);
            } else {
                if (node.renderType === 'port') {
                    this.portRender.addAsD3DataItem(node);
                } else if (node.renderType === 'instance') {
                    // 没有 skin 的器件或者端口
                    this.instantiationRender.addAsD3DataItem(node);
                } else if (node.renderType === 'constant') {
                    this.constantRender.addAsD3DataItem(node);
                }
            }

            // 如果存在 port，绘制 port
            for (const cellPort of node.ports || []) {
                this.connectionRender.addAsD3DataItem(cellPort, node); 
            }
        }

        const ports = this.portRender.render();
        const instances = this.instantiationRender.render();
        const cells = this.cellRender.render();
        const connections = this.connectionRender.render();
        const constants = this.constantRender.render();
        
        return { ports, instances, cells, connections };
    }

    /**
     * @description 绘制连线和交叉点
     * @param {d3.Selection} parentSelection 
     * @param {ElkNode} computedLayout 
     */
    async renderLine(parentSelection, computedLayout) {
        this.wireRender = new WireRender(parentSelection, this);
        this.crossDotRender = new CrossDotRender(parentSelection, this);

        // 建立关于 port 的索引
        const id2port = new Map();
        for (const node of computedLayout.children || []) {
            for (const port of node.ports || []) {
                id2port.set(port.id, port);
            }
        }

        const rangeTree = new RangeTreeMap();

        for (const edge of computedLayout.edges) {
            for (const section of edge.sections || []) {
                const points = [];
                points.push(section.startPoint);
                for (const point of section.bendPoints || []) {
                    points.push(point);
                }
                points.push(section.endPoint);
                this.wireRender.addAsD3DataItems(points, edge, id2port);

                // 加入 range tree 中
                for (let i = 0; i < points.length - 1; ++ i) {
                    rangeTree.insert({
                        x1: points[i].x,
                        y1: points[i].y,
                        x2: points[i + 1].x,
                        y2: points[i + 1].y
                    });
                }
            }
        }

        // 找出所有的交叉点
        for (const edge of computedLayout.edges) {
            for (const section of edge.sections || []) {
                // 交叉点一定不是起点和终点
                for (const point of section.bendPoints || []) {
                    const degree = rangeTree.getDegree(point);
                    if (degree >= 3) {
                        this.crossDotRender.addAsD3DataItem(point.x, point.y);
                    }
                }
            }
        }

        this.wireRender.render();
        this.crossDotRender.render();
    }

    /**
     * @description 注册平移和缩放
     * @param {d3.Selection} parentSelection 
     */
    registerRenderTransform(parentSelection) {
        // 创建缩放行为
        const zoom = d3.zoom()
            // 设置缩放范围（最小缩放比例 0.5，最大缩放比例 5）
            .scaleExtent([0.1, 10])
            .on("zoom", zoomed);

        // 将缩放行为应用到 SVG
        this.selection.call(zoom);
        this.zoom = zoom;

        let started = false;

        // 缩放事件处理函数
        function zoomed(event) {
            const { transform, sourceEvent } = event;

            if (sourceEvent && sourceEvent.type === "wheel") {
                if (globalSetting.renderAnimation) {
                    parentSelection
                        .transition()
                        .duration(350)
                        .attr('transform', transform);
                } else {
                    parentSelection.attr('transform', transform);
                }
            } else if (sourceEvent && sourceEvent.type === "mousemove") {
                if (globalSetting.renderAnimation) {
                    parentSelection.attr('transform', transform);
                } else {
                    parentSelection.attr('transform', transform);
                }
            } else {
                // 系统调用的变换，慢一点变换
                if (globalSetting.renderAnimation && !started) {
                    parentSelection
                        .attr('transform', transform)
                        .attr('opacity', 0)
                        .transition()
                        .duration(750)
                        .attr('opacity', 1)
                    started = true;
                } else {
                    parentSelection.attr('transform', transform);
                }
            }
        }
    }


    updateMaxMinSize(x, y, width, height) {
        let x1 = x;
        let y1 = y;
        let x2 = x1 + width;
        let y2 = y1 + height;

        this.minX = Math.min(this.minX, x1);
        this.maxX = Math.max(this.maxX, x2);
        this.minY = Math.min(this.minY, y1);
        this.maxY = Math.max(this.maxY, y2);
    }
    
    /**
     * @description 调整位姿
     * @param {d3.Selection} parentSelection 
     */
    adjustLocation(parentSelection) {
        const renderHeight = this.renderHeight;
        const renderWidth = this.renderWidth;
        // TODO: 更加智能的方位调整
        const cx = (this.minX + this.maxX) / 2;
        const cy = (this.minY + this.maxY) / 2;

        this.startOffsetX = renderWidth / 2 - cx;
        this.startOffsetY = renderHeight / 2 - cy;

        const transform = d3.zoomIdentity.translate(
            this.startOffsetX, this.startOffsetY
        );

        this.selection.call(
            this.zoom.transform,
            transform
        );
    }

    /**
     * @description 生成各个 manager 的拖拽上下文
     * @param {ElkNode} computedLayout 
     */
    createManagerDragContext(computedLayout) {
        const id2manager = this.id2manager;

        /**
         * @returns {DragNeighbor}
         */
        function makeDragNeighbor(id, portId) {
            if (id === portId) {
                // id 和 portId 都代表 port
                const manager = id2manager.get(id)[0];
                return {
                    type: 'port',
                    selection: manager.selection,
                    connections: []
                }
            } else {
                // id 代表 cell， portId 代表 connection    
                const cellManager = id2manager.get(id)[0];
                const connectionManager = id2manager.get(portId)[0];
                return {
                    type: 'cell',
                    selection: cellManager.selection,
                    connections: [
                        { selection: connectionManager.selection }
                    ]
                }
            }
        }

        for (const edge of computedLayout.edges) {
            // 遍历 edge，根据两侧 sourcePort 和 targetPort 将对应的 manager 中的 dragContext 进行更新
            const sourceNeighoor = makeDragNeighbor(edge.source, edge.sourcePort);
            const targetNeighoor = makeDragNeighbor(edge.target, edge.targetPort);

            sourceNeighoor.location = 'source';
            targetNeighoor.location = 'target';
            sourceNeighoor.edge = edge;
            targetNeighoor.edge = edge;

            const sourceManager = id2manager.get(edge.source)[0];
            const targetManager = id2manager.get(edge.target)[0];

            sourceManager.dragContext.neighbors.push(targetNeighoor);
            targetManager.dragContext.neighbors.push(sourceNeighoor);
        }
    }

    
    /**
     * @description 窗体大小发生变化时
     */
    registerResizeHandler(element) {
        this.resizeMonitor = new ResizeObserver(entries => {
            for (const entry of entries) {
                const { height, width } = entry.contentRect;
                this.renderHeight = height;
                this.renderWidth = width;
                if (this.selection) {
                    this.selection
                        .attr('width', this.renderWidth)
                        .attr('height', this.renderHeight)
                }
            }
        });
        this.resizeMonitor.observe(element);
    }

    /**
     * @description 展开例化模块
     * @param {d3.Selection} instanceSelection
     */
    async expandInstance(instanceSelection) {
        const data = instanceSelection.datum();
        const elkNode = data._self;
        const moduleName = elkNode.renderName;        
        const module = this.nameToModule.get(moduleName);

        // 先记录当前这个选择集的位置
        const originX = elkNode.x;
        const originY = elkNode.y;
        
        // 给所有的子图的节点增加前缀名
        function addNodeIdPrefix(nodes) {
            for (const node of nodes) {
                node.id = dotConnect(elkNode.name, node.id);
                for (const port of node.ports || []) {
                    port.id = dotConnect(elkNode.name, port.id);                    
                }
            }
        }

        // 给所有的子图的边增加前缀名
        function addEdgeIdPrefix(edges) {
            for (const edge of edges) {
                edge.id = dotConnect(elkNode.name, edge.id);
                edge.source = dotConnect(elkNode.name, edge.source);
                edge.sourcePort = dotConnect(elkNode.name, edge.sourcePort);
                edge.target = dotConnect(elkNode.name, edge.target);
                edge.targetPort = dotConnect(elkNode.name, edge.targetPort);
            }
        }

        const portNodes = module.makeNetsElkNodes();
        const cellNodes = module.makeCellsElkNodes();
        const [constantNodes, connectionEdges] = module.makeConnectionElkNodes();

        // 增加前缀
        addNodeIdPrefix(portNodes);
        addNodeIdPrefix(cellNodes);
        addNodeIdPrefix(constantNodes);
        addEdgeIdPrefix(connectionEdges);
        
        // 对于子图而言，不需要创建 ports，只需要把对应的连线接到所在例化模块的接口即可.
        // 只需要对当前 node 中的 port 进行 index，然后对于 connectionEdges 中对应的部分的 id 进行替换即可
        const portName2port = new Map();
        for (const port of elkNode.ports || []) {            
            portName2port.set(port.renderName, port);
        }

        const innerPortId2outterPortId = new Map();
        for (const innerPort of portNodes) {
            const outterPort = portName2port.get(innerPort.name);
            if (outterPort) {
                innerPortId2outterPortId.set(innerPort.id, outterPort.id);
            }
        }
                

        for (const edge of connectionEdges) {
            if (innerPortId2outterPortId.has(edge.source)) {
                const outterId = innerPortId2outterPortId.get(edge.source);
                edge.orginalSource = edge.source;
                edge.source = elkNode.id;
                edge.sourcePort = outterId;
            }

            if (innerPortId2outterPortId.has(edge.target)) {
                const outterId = innerPortId2outterPortId.get(edge.target);
                edge.orginalTarget = edge.target;
                edge.target = elkNode.id;
                edge.targetPort = outterId;
            }
        }

        // 加入到布局图中
        elkNode.children = [];
        elkNode.edges = [];
        elkNode.layoutOptions = this.defaultLayoutOptions;


        // elkNode.children.push(...portNodes);
        elkNode.children.push(...cellNodes);
        elkNode.children.push(...constantNodes);
        elkNode.edges.push(...connectionEdges);

        const start = performance.now();

        // 更新布局
        await this.elk.layout(this.elkGraph);
        const computedLayout = this.elkGraph;
        const timecost = (performance.now() - start).toFixed(3);

        pinkLog(`expandInstance 布局计算耗时 ${timecost} ms`);        
        console.log(computedLayout);

        // 计算 relayout 后的当前这个例化模块的位置偏移了多少，确保点击前后的左上角坐标一致
        // 以获得最佳的用户体验
        const deltaX = originX - elkNode.x;
        const deltaY = originY - elkNode.y;

        const transform = d3.zoomIdentity.translate(deltaX, deltaY);
        this.zoom.translateBy(this.selection, deltaX, deltaY);

        const svg = this.selection;
        const g = this.g;

        g.selectAll('*').remove();

        // 开始递归地进行渲染
        const renderStack = [
            {
                parentSelection: g,
                layout: computedLayout
            }
        ];

        while (renderStack.length > 0) {
            const s = renderStack.pop();
            const parentSelection = s.parentSelection;
            const layout = s.layout;

            await this.renderLine(parentSelection, layout);
            const { instances } = await this.renderEntity(parentSelection, layout);

            const id2selection = new Map();
            instances.each(function(data) {
                const selection = d3.select(this);
                id2selection.set(data.id, selection);
            });

            // 将需要渲染子图的部分扔进渲染器
            for (const node of layout.children || []) {
                const subChildren = node.children || [];
                
                if (subChildren.length > 0) {
                    const selection = id2selection.get(node.id);
                    renderStack.push({
                        parentSelection: selection,
                        layout: node
                    });
                }
            }
        }
    }


    /**
     * @description 关闭例化模块
     * @param {d3.Selection} instanceSelection
     */
    async collapseInstance(instanceSelection) {
        const data = instanceSelection.datum();
        const elkNode = data._self;

        // 找到所在模块
        const parentName = elkNode.parentName;
        const instanceName = elkNode.name;  
        const parentModule = this.nameToModule.get(parentName);
        const cell = parentModule.view.nameToCell.get(instanceName);

        // 先记录当前这个选择集的位置
        const originX = elkNode.x;
        const originY = elkNode.y;

        elkNode.children = [];
        elkNode.edges = [];
        // 重新计算 elkNode(例化模块) 的宽度和高度
        const instanceNode = parentModule.makeInstanceNode(cell);
        for (const key of Object.keys(instanceNode)) {
            elkNode[key] = instanceNode[key];
        }

        const start = performance.now();

        // 更新布局
        await this.elk.layout(this.elkGraph);
        const computedLayout = this.elkGraph;
        const timecost = (performance.now() - start).toFixed(3);

        pinkLog(`collapseInstance 布局计算耗时 ${timecost} ms`);        
        console.log(computedLayout);

        const deltaX = originX - elkNode.x;
        const deltaY = originY - elkNode.y;

        const transform = d3.zoomIdentity.translate(deltaX, deltaY);
        this.zoom.translateBy(this.selection, deltaX, deltaY);

        const svg = this.selection;
        const g = this.g;

        g.selectAll('*').remove();

        // 开始递归地进行渲染
        const renderStack = [
            {
                parentSelection: g,
                layout: computedLayout
            }
        ];

        while (renderStack.length > 0) {
            const s = renderStack.pop();
            const parentSelection = s.parentSelection;
            const layout = s.layout;
            await this.renderLine(parentSelection, layout);
            const { instances } = await this.renderEntity(parentSelection, layout);
            const id2selection = new Map();
            instances.each(function(data) {
                const selection = d3.select(this);
                id2selection.set(data.id, selection);
            });

            // 将需要渲染子图的部分扔进渲染器
            for (const node of layout.children || []) {
                const subChildren = node.children || [];
                if (subChildren.length > 0) {
                    const selection = id2selection.get(node.id);
                    renderStack.push({
                        parentSelection: selection,
                        layout: node
                    });
                }
            }   
        }
    }
}