import { MMap } from "../algorithm/mmap";
import { redLog } from "../utils";

/**
 * @typedef MergePortEntity
 * @property {'port'} type
 * @property {string} name
 * @property {'input' | 'output'} direction
 */

/**
 * @typedef MergeCellEntity
 * @property {'cell'} type
 * @property {string} name
 * @property {'input' | 'output'} direction
 * @property {import("../jsdoc").YosysCell} cell
 */

/**
 * @description 完成 yosys 的 module 的预处理，包括进行 wire id 的合并，常数字符串的合并
 * @param {import("../jsdoc").YosysNetModule} rawModule 
 */
function preprocessYosysRawModule(rawModule) {
    const mergeWireInfos = new Map();

    /**
     * @type {Map<number, (MergePortEntity | MergeCellEntity)[]>}
     */
    const id2entity = new Map();
    // const unvisitedIds = new Set();

    for (const [portName, port] of Object.entries(rawModule.ports)) {
        let constantNum = 0;
        for (const wireId of port.bits) {
            if (typeof wireId === 'string') {
                constantNum ++;
                continue;
            }

            const item = { name: portName, direction: port.direction, type: 'port' };
            if (!id2entity.has(wireId)) {
                id2entity.set(wireId, []);
            }

            id2entity.get(wireId).push(item);
        }

        if (constantNum === port.bits.length) {
            port.bits = [port.bits.join('')];
        }
    }

    for (const [cellName, cell] of Object.entries(rawModule.cells)) {
        for (const [connectionName, wireIds] of Object.entries(cell.connections)) {
            let constantNum = 0;

            for (const wireId of wireIds) {
                if (typeof wireId === 'string') {
                    constantNum ++;
                    continue;
                }

                const direction = cell.port_directions[connectionName];
                const item = { name: connectionName, cell, direction, type: 'cell' };
                if (!id2entity.has(wireId)) {
                    id2entity.set(wireId, []);
                }
                id2entity.get(wireId).push(item);
            }

            if (constantNum === wireIds.length) {
                cell.connections[connectionName] = [wireIds.join('')];
            }
        }
    }

    // 构建点对点映射
    const mmap = new MMap();
    for (const id of id2entity.keys()) {
        const entities = id2entity.get(id);
        
    }

    rawModule.mergeWireInfos = mergeWireInfos;
}

/**
 * @description 模块树对象，直接作为 treeview 的渲染视图加入运算
 * 相比于 YosysNetModule, ModuleView 只关心和渲染有关的那部分变量，且不可被序列化
 */
export class ModuleView {
    /**
     * @param {string} name
     * @param {import("../jsdoc").YosysNetModule} rawModule
     */
    constructor(name, rawModule) {
        this.name = name;
        preprocessYosysRawModule(rawModule);
        this.rawModule = rawModule;

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

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

        // 给非常数的 wireId 构建映射
        // 因为非常数类型的 wireId 只关联 port 和 connection

        /**
         * @type {Map<number, Port>}
         */
        this._wireIdToPort = new Map();

        /**
         * @type {Map<number, Connection>}
         */
        this._wireIdToConnection = new Map();

        for (const [portName, rawPort] of Object.entries(rawModule.ports)) {
            const port = new Port(this, portName, rawPort);
            this._nameToPort.set(portName, port);
            // 构建 信号线 到 port 的映射
            for (const id of port.bits) {
                if (typeof id === 'number') {
                    this._wireIdToPort.set(id, port);
                }
            }    
        }

        
        for (const [cellName, rawCell] of Object.entries(rawModule.cells)) {            
            const cell = new Cell(this, cellName, rawCell);
            this._nameToCell.set(cellName, cell);
            // 构建映射
            for (const connection of cell.nameToConnection.values()) {
                for (const wire of connection.wires) {
                    const id = wire.wireId;
                    if (typeof id === 'number') {
                        this._wireIdToConnection.set(id, connection);
                    }
                }
            }
        }
    }

    /**
     * @returns {Map<string, Port>}
     */
    get nameToPort() {
        return this._nameToPort;
    }

    /**
     * @returns {Map<string, Cell>}
     */
    get nameToCell() {
        return this._nameToCell;
    }

    /**
     * @type {Map<number, Port>}
     */
    get wireIdToPort() {
        return this._wireIdToPort;
    }

    /**
     * @type {Map<number, Connection>}
     */
    get wireIdToConnection() {
        return this._wireIdToConnection;
    }

    get id() {
        return this.name;
    }
}

export class Port {
    /**
     * @description port 的抽象
     * @param {ModuleView} view
     * @param {string} name
     * @param {YosysPort} rawPort 
     */
    constructor(view, name, rawPort) {
        this.view = view;
        this.name = name;
        this.rawPort = rawPort;
    }

    get bits() {
        return this.rawPort.bits;
    }

    get direction() {
        return this.rawPort.direction;
    }

    get id() {
        return dotConnect(this.view.id, this.name);
    }
}

export class Cell {
    /**
     * @description 器件的抽象
     * @param {ModuleView} view
     * @param {string} name 
     * @param {YosysCell} rawCell 
     */
    constructor(view, name, rawCell) {
        this.view = view;
        this.name = name;        
        this.rawCell = rawCell;

        /**
         * @description 是否为例化模块
         */
        this.isInstantiation = false;

        /**
         * @description 如果是例化模块，它对应的 module 的 tree
         * @type {ModuleView}
         */
        this.belongModuleView = undefined;

        this._nameToConnection = new Map();

        for (const [connectionName, wireIds] of Object.entries(rawCell.connections)) {
            const connection = new Connection(this, connectionName, wireIds);
            this._nameToConnection.set(connectionName, connection);
        }
    }

    /**
     * @returns {Map<string, Connection>}
     */
    get nameToConnection() {
        return this._nameToConnection;
    }

    /**
     * @description 当前器件的类型
     * - 如果器件是一个例化模块，那么 type 就是这个例化的 module 的名字，比如 `full_adder_1bit`
     * - 如果器件是一个基本器件，那么 type 就是 yosys 文档中记录的关于这个器件的名字，比如 $_AND_(与门), $_OR_(或门)
     */
    get type() {
        return this.rawCell.type;
    }

    get id() {
        return dotConnect(this.view.id, this.name);
    }
}


export class Connection {
    /**
     * @description connection 的抽象，一个 connection 代表一个器件的一个端口是如何与外界连接的
     * @param {Cell} cell 
     * @param {string} name 
     * @param {WireId[]} wireIds 
     */
    constructor(cell, name, wireIds) {
        this.cell = cell;
        this.name = name;
        this.wireIds = wireIds;

        /**
         * @type {PortDirection}
         */
        this.direction = cell.rawCell.port_directions[name];

        /**
         * @type {Wire[]}
         */
        this.wires = [];

        for (let i = 0; i < wireIds.length; ++ i) {
            const wire = new Wire(this, i, wireIds[i]);
            this.wires.push(wire);
        }
    }

    get id() {
        return dotConnect(this.cell.id, this.name);
    }
}

export class Wire {
    /**
     * @description 一根线的抽象
     * @param {Connection} connection 
     * @param {number} index
     * @param {WireId} wireId 
     */
    constructor(connection, index, wireId) {
        this.connection = connection;
        this.index = index;
        this.wireId = wireId;
    }

    get id() {
        if (typeof this.wireId === 'string') {
            // 常数连接
            const num = parseInt(this.wireId, 2);
            return dotConnect(this.connection.id, this.index, 'c-' + num);
        } else {
            // 普通连接
            return dotConnect(this.connection.id, this.wireId);
        }
    }
}

/**
 * @description 使用 . 把各个部分连接起来
 * @param {any[]} args
 */
export function dotConnect(...args) {
    const stringArgs = args.map(arg => arg.toString());
    return stringArgs.join('.');
}