import { assert } from "../../../assert.js";
import { objectKeyByValue } from "../../object/objectKeyByValue.js";
import { DataType } from "../type/DataType.js";
import { deserializeDataTypeFromJSON } from "../type/deserializeDataTypeFromJSON.js";
import { PortDirection } from "./PortDirection.js";

/**
 * @type {number}
 */
let id_counter = 0;

export class Port {
    /**
     * Human-readable label
     * @type {String}
     */
    name = "";

    /**
     * ID uniquely identifies object within some context. Ids are assumed to be immutable
     * @type {number}
     */
    id = id_counter++;

    /**
     *
     * @type {PortDirection|number}
     */
    direction = PortDirection.Unspecified;

    /**
     *
     * @type {DataType}
     */
    dataType = null;

    /**
     * Unstructured user data
     * @type {object}
     */
    metadata = {};

    /**
     *
     * @returns {number}
     */
    hash() {
        return this.id;
    }

    /**
     *
     * @param {Port} other
     * @returns {boolean}
     */
    equals(other) {

        if (this === other) {
            // special case of self-equality
            return true;
        }

        return this.id === other.id
            && this.name === other.name
            && this.direction === other.direction
            && this.dataType === other.dataType
            ;

    }

    toString() {
        return `Port{ id=${this.id}, direction=${this.direction}, name=${this.name}, type=${this.dataType} }`;
    }

    toJSON() {
        return {
            id: this.id,
            name: this.name,
            direction: objectKeyByValue(PortDirection, this.direction),
            dataType: this.dataType !== null ? this.dataType.toJSON() : null
        };
    }

    /**
     *
     * @param {number} id
     * @param {string} name
     * @param {string} direction
     * @param {*} dataType
     * @param {NodeRegistry} [registry] to avoid creating copies of data types
     */
    fromJSON({
                 id, name, direction, dataType
             }, registry) {

        assert.isNonNegativeInteger(id, 'id');

        this.id = id;
        this.name = name;
        this.direction = PortDirection[direction];

        let _type = dataType !== null ? deserializeDataTypeFromJSON(dataType) : null;

        if (registry !== undefined && _type !== null) {
            assert.equal(registry.isNodeRegistry, true, 'registry.isNodeRegistry !== true');

            // attempt to re-write data type with one from the registry
            const match = registry.getTypeById(_type.id);

            if (match !== undefined) {

                if (!match.equals(_type)) {
                    throw new Error(`Loaded type ${_type} does not match one in the registry ${match}`)
                }

                // re-write type
                _type = match;
            } else {
                throw new Error(`Loaded type ${_type} was not found in the registry`);
            }
        }

        this.dataType = _type;

    }

    /**
     *
     * @param j
     * @param {NodeRegistry} registry
     * @returns {Port}
     */
    static fromJSON(j, registry) {
        const r = new Port();

        r.fromJSON(j, registry);

        return r;
    }
}

/**
 * @readonly
 * @type {boolean}
 */
Port.prototype.isPort = true;
