/**
 * 将各个节点转换为 elknode 的数据结构
 */

import { globalLookup } from "../global";
import { Cell, dotConnect, ModuleView } from "./yosys";

// 线段的宽度
export const LINE_WIDTH = 2;
// 分组最小 ID
export const MIN_PARTITION_INDEX = 1;
// 分组最大 ID
export const MAX_PARTITION_INDEX = 10000000;
// 每一个例化最多能占据的列数
export const MAX_INSTANCE_COL_NUM = 1000;
// 每一列最多的同类器件数量
export const MAX_SAME_TYPE_INSTANCE_NUM = 5;
// 其他常量
export const LAYOUT_CONSTANT = {
    // port
    PORT_WIDTH: 50,
    PORT_HEIGHT: 25,
    
    // 例化模块（这只是最小，height 会根据端口数量进行调整）
    INSTANTIATION_WIDTH: 80,
    INSTANTIATION_HEIGHT: 80,

    // port 顶部的空余，一般用来放置标签的
    PORT_TOP_MARGIN: 30,
    // port 内部的 padding，用于绘制
    PORT_INNER_PADDING: 20,

    // 例化模块上部分的空缺区域
    INSTANCE_TITLE_HEIGHT: 10,
    INSTANCE_TOP_PADDING: 20,
    INSTANCE_LEFT_MARGIN: 10,
    INSTANCE_RIGHT_MARGIN: 10,

    // 常数
    CONSTANT_WIDTH: 50,
    CONSTANT_HEIGHT: 30,

    // 器件的端口
    CELL_PORT_HEIGHT: 1,
    CELL_PORT_WIDTH: 1,
    CELL_LEFT_MARGIN: 10,
    CELL_RIGHT_MARGIN: 10
};

export const ELK_DIRECTION = {
    LEFT: 'WEST',
    RIGHT: 'EAST',
    TOP: 'NORTH',
    BOTTOM: 'SOUTH'
}


export function makeEdgeId(fromId, toId) {
    return fromId + '-' + toId;
}

export class Module {
    /**
     * @param {string} name
     * @param {YosysNetModule} module 
     */
    constructor(name, module) {
        this.module = module;
        this.name = name;

        /**
         * @description
         * @type {ModuleView}
         */
        this.view = new ModuleView(name, module);

        this.commonPartitionIndex = 10;
        this.partitionIndex = 11;

        /**
         * @description 统计一个例化模块 name 出现的次数，用来分配布局分组索引
         * @type {Map<string, number>}
         */
        this.instanceNameCounter = new Map();
    }
    
    /**
     * @description 从 ports 中创建所有变量相关的节点
     * @returns {ElkNode[]}
     */
    makeNetsElkNodes() {
        const nodes = [];

        // 绘制 ports
        for (const name of this.view.nameToPort.keys()) {
            const port = this.view.nameToPort.get(name);

            // 根据长度估算 name 的渲染长度
            const estimateLength = Math.max(12 * 0.6 * name.length + 10, LAYOUT_CONSTANT.PORT_WIDTH);

            if (port.direction === 'input') {
                // 为 port 设置连接点
                const portConnection = {
                    id: dotConnect(port.id, '0'),
                    renderType: 'portConnection',
                    source: 'port',
                    // 对于 input 的 port 而言，它的端口只有一个且为输出
                    direction: 'output',
                    width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
                    height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
                    x: estimateLength,
                    y: LAYOUT_CONSTANT.PORT_HEIGHT / 2
                };
                
                const node = {
                    id: port.id,
                    name: port.name,
                    renderName: name,
                    renderType: 'port',
                    source: 'port',
                    // 对于 input 的 port 而言，它的端口只有一个且为输入
                    direction: 'input',
                    type: port.direction,
                    width: estimateLength,
                    height: LAYOUT_CONSTANT.PORT_HEIGHT,
                    ports: [portConnection],
                    layoutOptions: {
                        'partitioning.partition': MIN_PARTITION_INDEX,
                        'org.eclipse.elk.portConstraints': 'FIXED_POS'
                    }
                };
    
                nodes.push(node);
            } else {
                // 为 port 设置连接点
                const portConnection = {
                    id: dotConnect(port.id, '0'),
                    renderType: 'portConnection',
                    width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
                    height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
                    x: 0,
                    y: LAYOUT_CONSTANT.PORT_HEIGHT / 2
                };

                const node = {
                    id: port.id,
                    name: port.name,
                    renderName: name,
                    renderType: 'port',
                    type: port.direction,
                    width: estimateLength,
                    height: LAYOUT_CONSTANT.PORT_HEIGHT,
                    ports: [portConnection],
                    layoutOptions: {
                        'partitioning.partition': MAX_PARTITION_INDEX,
                        'org.eclipse.elk.portConstraints': 'FIXED_POS'
                    }
                };
    
                nodes.push(node);
            }

        }

        // 非 port 的其他内部变量的节点不需要绘制，因为它们总能被表示为中间变量
        // 它们的操作可以表示为一系列器件的组合或者直连

        return nodes;
    }

    /**
     * @description 从 cells（器件） 中创建节点
     * @returns {[ElkNode[], ElkEdge[]]}
     */
    makeCellsElkNodes() {
        const nodes = [];
        const edges = [];

        const skinManager = globalLookup.skinManager;
        for (const name of this.view.nameToCell.keys()) {
            const cell = this.view.nameToCell.get(name);
            const skin = skinManager.querySkin(cell.type);

            if (skin) {
                const node = this.makeCellNode(cell, skin);
                nodes.push(node);
            } else {
                const node = this.makeInstanceNode(cell);
                nodes.push(node);
            }
        }

        return nodes;
    }

    /**
     * @description 从 cells.connections 中创建边
     * @returns {[ElkNode[], ElkEdge[]]}
     */
    makeConnectionElkNodes() {
        const nodes = [];
        const edges = [];

        const tree = this.view

        for (const cellName of tree.nameToCell.keys()) {
            const cell = tree.nameToCell.get(cellName);
            
            for (const connectionName of cell.nameToConnection.keys()) {
                const connection = cell.nameToConnection.get(connectionName);
                // 遍历器件端口的每一个连接点
                // 比如对于端口 input [31:0] data ，它的32个位不一定是完全导向一个变量（虽然我们愿意认为，大部分情况下是这样）
                // 大部分情况下，👇的 wireIds.length 都是 1 
                for (const wire of connection.wires) {
                    const wireId = wire.wireId;
                    const id = wire.id;
                    if (typeof wireId === 'string') {
                        // 常数
                        // 如果是常数，需要先创建代表常数的节点，常数一定是器件的输入，而非输出

                        // 估算常量的宽度
                        let renderString = parseInt(wireId, 2);
                        if (isNaN(renderString)) {
                            if (wireId.toString().startsWith('x')) {
                                renderString = `${wireId.toString().length}{1'bx}`;
                            } else {
                                renderString = wireId.toString();
                            }
                        }
                        
                        const constantWidth = Math.max(12 * 0.6 * renderString.toString().length + 10, LAYOUT_CONSTANT.CONSTANT_WIDTH);
                        console.log(constantWidth);
                        
                        const constantConnection = {
                            id: dotConnect(id, '0'),
                            renderType: 'portConnection',
                            source: 'port',
                            // 对于 constant 的 port 而言，它的端口只有一个且为输出
                            direction: 'output',
                            width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
                            height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
                            x: constantWidth,
                            y: LAYOUT_CONSTANT.CONSTANT_HEIGHT / 2
                        };
                        
                        const node = {
                            id,
                            name: wireId,
                            renderType: 'constant',
                            width: constantWidth,
                            height: LAYOUT_CONSTANT.CONSTANT_HEIGHT,
                            ports: [constantConnection],
                            layoutOptions: {
                                'partitioning.partition': this.commonPartitionIndex,
                                'org.eclipse.elk.portConstraints': 'FIXED_POS'
                            }
                        };

                        nodes.push(node);

                        // 创建常数到器件的连线
                        const edge = {
                            id: makeEdgeId(cell.id, id),
                            source: id,
                            sourcePort: constantConnection.id,
                            target: cell.id,
                            targetPort: connection.id
                        };

                        edges.push(edge);
                    } else {
                        // 如果不是一个常数连接，那么存在两种可能
                        // 1. 当前的器件的这个端口和某一个 port 连接
                        // 2. 当前的器件的这个端口和另一个器件的一个端口连接
                        if (tree.wireIdToPort.has(wireId)) {

                            // 当前的器件的这个端口和某一个 port 连接
                            const port = tree.wireIdToPort.get(wireId);

                            if (port.direction === 'input') {
                                const edge = {
                                    // id 遵循 sourcePort-targetPort
                                    id: makeEdgeId(port.id, connection.id),
                                    source: port.id,
                                    sourcePort: dotConnect(port.id, '0'),
                                    target: cell.id,
                                    targetPort: connection.id
                                };
                                
                                edges.push(edge);
                            } else {
                                const edge = {
                                    id: makeEdgeId(connection.id, port.id),
                                    source: cell.id,
                                    sourcePort: connection.id,
                                    target: port.id,
                                    targetPort: dotConnect(port.id, '0')
                                };    
                                edges.push(edge);
                            }
                        } else if (tree.wireIdToConnection.has(wireId)) {
                            
                            // 当前的器件的这个端口和另一个器件的一个端口连接
                            const conn = tree.wireIdToConnection.get(wireId);

                            // 防止查询到自己
                            if (conn.id === connection.id && conn.cell.id === cell.id) {
                                continue;
                            }

                            if (conn.direction === 'input') {
                                const edge = {
                                    // id 遵循 sourcePort-targetPort
                                    id: makeEdgeId(conn.id, connection.id),
                                    source: cell.id,
                                    sourcePort: connection.id,
                                    target: conn.cell.id,
                                    targetPort: conn.id
                                };
    
                                edges.push(edge);
                            } else {
                                const edge = {
                                    id: makeEdgeId(connection.id, conn.id),
                                    source: conn.cell.id,
                                    sourcePort: conn.id,
                                    target: cell.id,
                                    targetPort: connection.id
                                };
                                edges.push(edge);
                            }
                        }
                    }
                }

            }
        }

        return [nodes, edges];
    }

    /**
     * @description 制作器件对应的 node
     * @param {Cell} cell
     * @param {import("../skin").SkinResource} skin  
     */
    makeCellNode(cell, skin) {
        // 内置器件
        // 创建器件节点的 port, port 和 connection 一一对应
        const meta = skin.meta;
        
        const height = meta.height;
        const width = meta.width + LAYOUT_CONSTANT.CELL_LEFT_MARGIN + LAYOUT_CONSTANT.CELL_RIGHT_MARGIN;

        const ports = [];

        // 统计分配到左右两侧的 port
        const leftSideConnections = [];
        const rightSideConnections = [];

        for (const conn of cell.nameToConnection.values()) {
            if (conn.direction === 'input') {
                leftSideConnections.push(conn);
            } else {
                rightSideConnections.push(conn);
            }
        }

        // 计算左侧的
        for (let i = 0; i < leftSideConnections.length; ++ i) {
            const connection = leftSideConnections[i];
            const yOffset = meta.getPortYOffset(connection.name);

            ports.push({
                id: connection.id,
                renderName: connection.name,
                renderType: 'cellPort',
                direction: 'input',
                source: 'cell',
                width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
                height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
                x: 0,
                y: yOffset
            });
        }

        // 计算右侧的
        for (let i = 0; i < rightSideConnections.length; ++ i) {
            const connection = rightSideConnections[i];
            const yOffset = meta.getPortYOffset(connection.name);                  

            ports.push({
                id: connection.id,
                renderName: connection.name,
                renderType: 'cellPort',
                direction: 'output',
                source: 'cell',
                width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
                height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
                x: width,
                y: yOffset
            });
        }

        const node = {
            id: cell.id,
            name: cell.name,
            renderName: cell.type,
            renderType: 'cell',
            width,
            height,
            ports,
            layoutOptions: {
                'org.eclipse.elk.portConstraints': 'FIXED_POS',
                'partitioning.partition': this.commonPartitionIndex
            }
        };

        return node;
    }

    /**
     * @description 制作例化模块对应的 node
     * @param {Cell} cell 
     */
    makeInstanceNode(cell) {
        // 统计分配到左右两侧的 port
        const leftSideConnections = [];
        const rightSideConnections = [];

        for (const conn of cell.nameToConnection.values()) {
            if (conn.direction === 'input') {
                leftSideConnections.push(conn);
            } else {
                rightSideConnections.push(conn);
            }
        }

        // 根据一侧的 port 数量来决定 height
        const maxPortNum = Math.max(leftSideConnections.length, rightSideConnections.length);
        const instanceHeight = Math.max(
            // 这是例化模块高度的最小值
            LAYOUT_CONSTANT.INSTANTIATION_HEIGHT,
            // 每一个 connection 占据的空间为：【上方空余区域（用来防止文本的）】 + 【本身的高度】
            (LAYOUT_CONSTANT.PORT_TOP_MARGIN + LAYOUT_CONSTANT.CELL_PORT_HEIGHT) * (maxPortNum + 1)
        );
        // 宽度等于预设宽度 + 两倍的 inner padding + left margin + right margin
        const instanceWidth = LAYOUT_CONSTANT.INSTANTIATION_WIDTH +
                              2 * LAYOUT_CONSTANT.PORT_INNER_PADDING +
                              LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN +
                              LAYOUT_CONSTANT.INSTANCE_RIGHT_MARGIN;
                
        // 创建器件节点的 port, port 和 connection 一一对应
        const ports = [];
        let inputCount = 0;
        let outputCount = 0;              
        for (const connectionName of cell.nameToConnection.keys()) {                    
            const connection = cell.nameToConnection.get(connectionName);                
            const portSide = connection.direction === 'input' ? ELK_DIRECTION.LEFT: ELK_DIRECTION.RIGHT;
            if (connection.direction === 'input') {
                inputCount ++;
                const offsetY = inputCount * LAYOUT_CONSTANT.PORT_TOP_MARGIN + 
                                (inputCount - 1) * LAYOUT_CONSTANT.CELL_PORT_HEIGHT + 
                                LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT + LAYOUT_CONSTANT.INSTANCE_TOP_PADDING;

                ports.push({
                    id: connection.id,
                    renderName: connectionName,
                    renderType: 'cellPort',
                    direction: 'input',
                    source: 'instance',
                    width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
                    height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
                    x: 0,
                    y: offsetY  
                });
            } else {
                outputCount ++;
                const offsetY = outputCount * LAYOUT_CONSTANT.PORT_TOP_MARGIN + 
                                (outputCount - 1) * LAYOUT_CONSTANT.CELL_PORT_HEIGHT +
                                LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT + LAYOUT_CONSTANT.INSTANCE_TOP_PADDING;

                ports.push({
                    id: connection.id,
                    renderName: connectionName,
                    renderType: 'cellPort',
                    direction: 'output',
                    source: 'instance',
                    width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
                    height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
                    x: instanceWidth,
                    y: offsetY
                });
            }
        }

        
        const node = {
            id: cell.id,
            name: cell.name,
            parentName: this.name,
            renderName: cell.type,
            renderType: 'instance',
            width: instanceWidth,
            height: instanceHeight + LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT + LAYOUT_CONSTANT.INSTANCE_TOP_PADDING,
            ports,
            layoutOptions: {
                // 强制固定 port 的方向
                'org.eclipse.elk.portConstraints': 'FIXED_POS',
                // TODO: 同名例化模块对齐
                'partitioning.partition': this.commonPartitionIndex
            }
        };

        return node;
    }
}

/**
 * 
 * @param {import('../jsdoc').ElkPort} port 
 * @returns {ConnectionMarginParameter}
 */
export function getMarginParamter(port) {
    if (!port) {
        // 如果是例化模块，那么当前的 port 就是空
        // 因为例化模块内部的变量一定和外部直连，所以例化模块内部是没有 port 的
        return {
            leftMargin: 0,
            rightMargin: 0
        }
    }
    switch (port.source) {
        case 'instance':
            return {
                leftMargin: LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN,
                rightMargin: LAYOUT_CONSTANT.INSTANCE_RIGHT_MARGIN
            }
        case 'cell':
            return {
                leftMargin: LAYOUT_CONSTANT.CELL_LEFT_MARGIN,
                rightMargin: LAYOUT_CONSTANT.CELL_RIGHT_MARGIN
            }
        default:
            return {
                leftMargin: 0,
                rightMargin: 0
            }
    }
}

/**
 * @typedef ConnectionMarginParameter
 * @property {number} leftMargin
 * @property {number} rightMargin
 */