Source: configurator/connectable.js

import { BuildableComponent } from "../package/component/buildable_component.js";
import { Reporter } from "../reporter/reporter.js";
import { Configuration } from "./configuration.js";
import { checkPropTypes } from '../lib.js';

// consideration:
// might be helpful to create phantom connectors instances that can be either connected or not
// simply to track state
// but these will not have an id, so the connection can not refer to them directly
// then what is the use?


const emptyConnectorTemplate = {
    connectors: []
}

/**
 * Base class for instantiated connectable elements, such as BlockInstance (which
 * instantiates Block) and Connection (which can be seen as an instance of ConnectionType).
 * @class
 */
class Connectable extends BuildableComponent {

    /**
     * 
     * @param {Reporter} reporter 
     * @param {Object} settings
     */
    // * @param {BuildableComponent} settings.connectorTemplate
    // * @param {Array<Connector>} settings.connectors

    constructor(reporter, settings) {


        super(
            reporter,
            settings
        );

        if (!this.constructor.connectorTemplateSetting) {
            throw new Error(`${this.constructor.name} is missing static property connectorTemplateSetting`);
        }

        // ideally, a connectable has a connector template on construction
        // alas, for a connection, the connectionType is the connection template
        // and when it is constructed, the connector type is still unknown
        // therefore, when a connectable doesn't specify a connector template
        // on initialisation, the emptyConnectorTemplate is used as placeholder
        // until the ._tree field is set

        checkPropTypes(
            settings,
            {},
            {
                [this.constructor.connectorTemplateSetting]: [

                    BuildableComponent,

                ]
            }
        );

        if (settings[this.constructor.connectorTemplateSetting]) {
            this.connectorTemplate = settings[this.constructor.connectorTemplateSetting];
        }
        else {
            this.connectorTemplate = emptyConnectorTemplate;
        }





    }

    clone(newSettings = {}) {
        const cl = super.clone(newSettings);
        cl.connectorTemplate = this.connectorTemplate;
        return cl;
    }

    /**
     * The Component that specifies which connectors this Connectable has
     * @type {Component}
     */

    connectorTemplate;


    /**
     * All connections of which this connectable is a part
     * @type {Array<Connection>} 
     */

    connections;


    /**
     * All equal connections (no parent/child relationship) of which this connectable is a part
     * @type {Array<Connection>}
     */

    equalConnections;


    /**
     * All unequal connections (with parent/child relationship) of which this connection is a part
     * @type {Array<Connection>} 
     */

    unequalConnections;


    /**
     * All other connectables this one is connected to 
     * @type {Array<Connectable>} 
     */

    connectedTo;


    /**
     * A Connectable that acts as a sibling to this one
     * @type {(Array<Connectable>)}
     */

    siblings;


    /**
     * A Connectable that acts as a parent to this one
     * @type {(Connection)}
     */

    parentConnection;

    /**
     * A Connectable that acts as a parent to this one
     * @type {(Connectable)}
     */

    parent;


    /**
     * Connectables that acts as children to this one
     * @type {Array<Connection>}
     */

    childConnections;

    /**
     * Connectables that acts as children to this one
     * @type {Array<Connectable>}
     */

    children;


    /**
     * 
     */

    childMaterialAssignments;


    get siblingChains() {

        const siblingsInAChain = [];
        const chains = {};

        for ( let sibling of this.siblings || [] ) {
            // console.log(`${this.label} find siblings of sibling ${sibling.label}`);
            const chain = [ sibling, ...Connectable.findSiblings(sibling,[this, ...siblingsInAChain])];  // safeguard against circular designs
            chains[ sibling.id ] = chain;
            siblingsInAChain.push(...chain);
        }

        return chains;
    };


    findMaterialAssignments() {
        if ( this._tree && this._tree.materialAssignments ) {
            return this._tree.materialAssignments.filter(ma => ma.blockInstance === this);
        }
        else {
            return [];
        }
    }



    static findSiblings = (connectable, excludeList = []) => {
        // console.log( 'conn', connectable, 'excl', excludeList);
        return ( connectable.siblings || [] )
            .filter(sibling => !excludeList.includes(sibling))
            .map(sibling => [ sibling, ...Connectable.findSiblings( sibling, [connectable, ...excludeList])])
            .flat();
    }



    _updateConnections() {

        // console.log( 'update connections', this._tree.connections.length )

        this.materialAssignments = this.findMaterialAssignments();

        this.connections = (this._tree.connections || []).filter(cnn =>
            (cnn.from.connectable === this && this.connectorTemplate.connectors.includes(cnn.from.connector))
            ||
            (cnn.to.connectable === this && this.connectorTemplate.connectors.includes(cnn.to.connector))
        );

        this.equalConnections = this.connections.filter(cnn => cnn.type.parent === undefined);
        this.unequalConnections = this.connections.filter(cnn => cnn.type.parent !== undefined);

        this.connectedTo = this.connections.map(cnn => cnn.from.connectable === this ? cnn.to.connectable : cnn.from.connectable);

        this.siblings = this.equalConnections.map(cnn => cnn.from.connectable === this ? cnn.to.connectable : cnn.from.connectable);

        // this.siblingChains = {};

        

        // It is known that the unequal connections can not have the same connector type twice
        // and it is known that one of the two sides must be the parent. So, if the from side
        // connectable equals this and the from side connector is the parent type, this is
        // the parent, otherwise this is the child

        const parentConnection = this.unequalConnections.find(cnn => {
            const mySide = cnn.from.connectable === this ? 'from' : 'to';
            return cnn[mySide].connector === cnn.type.parent;
        });

        if (parentConnection) {
            this.parentConnection = parentConnection;
            const parentSide = parentConnection.from.connectable === this ? 'to' : 'from';
            this.parent = parentConnection[parentSide].connectable;
        }

        const childConnections = this.unequalConnections
            .filter(cnn => cnn !== parentConnection);

        if (childConnections) {

            this.childConnections = childConnections;

            this.children = childConnections
                .map(ucnn => ucnn.connectables.find(ctb => ctb !== this));

            this.childMaterialAssignments = this.children.map(child => child.findMaterialAssignments()).flat();
        }
    }

    _onTreeSet() {

        // console.error( 'tree set', this._tree.label)

        console.assert(this._tree instanceof Configuration, `${this.constructor.name} can not be added to a ${this._tree.constructor.name}`);

        const component = this;

        if (!this._tree.findComponentById(this.connectorTemplate.id)) {
            throw new Error(`ConnectorTemplate ${this.connectorTemplate.label} not in tree`);
        }

        this._tree.on(
            'change',
            ({ cls, type }) => {
                // console.log( 'change', type, cls)
                if (cls.find( cl => cl.name === 'Connection')) {
                    component._updateConnections();
                }
            },
            this.id
        );

        this._updateConnections();
    }

    _onTreeUnset() {
        this._tree.removeListeners('change', this.id);
        this.connections = undefined;
        this.connectedTo = undefined;
        this.materialAssignments = undefined;
        this.childMaterialAssignments = undefined;
    }



}

export { Connectable };