import { assert } from "../../../assert.js";
import { combine_hash } from "../../../collection/array/combine_hash.js";

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

/**
 * Reference for a port of a node instance
 */
export class NodeInstancePortReference {

    /**
     * @readonly
     * @type {number}
     */
    id = id_counter++;

    /**
     * @readonly
     * @type {NodeInstance}
     */
    instance = null;

    /**
     * @readonly
     * @type {Port}
     */
    port = null;

    /**
     * Attached connections
     * NOTE: Maintained by NodeGraph, do not modify
     * @readonly
     * @type {Connection[]}
     */
    connections = [];

    /**
     * Outgoing connections
     * @return {Connection[]}
     */
    get outConnections() {
        return this.connections.filter(c => c.source === this);
    }

    /**
     * Incoming connections
     * @return {Connection[]}
     */
    get inConnections() {
        return this.connections.filter(c => c.target === this);
    }

    /**
     * Is anything connected to this endpoint?
     * Shortcut for `connections.length > 0`
     *
     * @return {boolean}
     */
    hasConnections() {
        return this.connections.length > 0;
    }

    /**
     * Checks if a connection exists from this endpoint to a specific node instance
     * Also returns true when parameter refers to instance that the port is attached to
     * @param {number} node_instance_id
     * @returns {boolean}
     */
    isConnectedToNode(node_instance_id) {
        assert.isNonNegativeInteger(node_instance_id, 'node_instance_id');

        if (this.instance.id === node_instance_id) {
            return true;
        }

        const connections = this.connections;
        const connection_count = connections.length;

        for (let i = 0; i < connection_count; i++) {
            const connection = connections[i];

            if (connection.isAttachedToNode(node_instance_id)) {
                return true;
            }
        }

        return false;
    }


    /**
     *
     * @param {NodeInstance} instance
     * @param {Port} port
     * @returns {NodeInstancePortReference}
     */
    static from(instance, port) {
        const r = new NodeInstancePortReference();

        r.set(instance,port);

        r.id = port.id;

        return r;
    }

    /**
     *
     * @param {NodeInstance} instance
     * @param {Port} port
     */
    set(instance, port) {
        assert.defined(instance, 'instance');
        assert.notNull(instance, 'instance');
        assert.equal(instance.isNodeInstance, true, 'instance.isNodeInstance !== true');

        assert.defined(port, 'port');
        assert.notNull(port, 'port');
        assert.equal(port.isPort, true, 'port.isPort !== true');

        this.instance = instance;
        this.port = port;
    }

    /**
     *
     * @return {number}
     */
    hash() {
        return combine_hash(
            this.instance.hash(),
            this.port.hash()
        );
    }

    /**
     *
     * @param {NodeInstancePortReference} other
     * @returns {boolean}
     */
    equals(other) {
        return this.instance.equals(other.instance)
            && this.port.equals(other.port)
            ;
    }

    toString() {
        return `NodeInstancePortReference{ id=${this.id}, instance=${this.instance}, port=${this.port}, connections.length=${this.connections.length} }`;
    }
}

/**
 * @readonly
 * @type {boolean}
 */
NodeInstancePortReference.prototype.isNodeInstancePortReference = true;
