Source: utilities/findConfigOptions.js

import { Quaternion, Vector3 } from '../../node_modules/three/build/three.module.js';
import { BlockInstance } from '../configurator/block_instance.js';
import { Configuration } from "../configurator/configuration.js";

import { omit, checkPropTypes, unique } from '../lib.js';
import { Block } from '../package/block/block.js';
import { Connector } from '../package/connector/connector.js';
import { ConnectorType } from '../package/connector/connector_type.js';
import { ConnectionType } from '../package/connector/connection_type.js';
import { Reporter } from '../reporter/reporter.js';
import { Package } from '../package/package.js';

import { ComponentTree } from '../component/component_tree.js';
import { Component } from '../component/component.js';
import { Connectable } from '../configurator/connectable.js';
import { Connection } from '../configurator/connection.js';
import { MaterialAssignment } from '../configurator/material_assignment.js';
import { WrappedMesh } from '../package/mesh/wrapped_mesh.js';
import { MaterialSet } from '../package/material/material_set.js';
import { MaterialCategory } from '../package/material/material_category.js';
import { calculatePlacement } from '../utilities/calculatePlacement.js';
import findConnectedConnectors from '../utilities/findConnectedConnectors.js';

import { ConfigurationInfo } from '../configurator/configuration_info.js';

/** @typedef {import("../configurator/configuration.js").InsertOption} InsertOption */
/** @typedef {import("../configurator/configuration.js").ExtendOption} ExtendOption */
/** @typedef {import("../configurator/configuration.js").CutOption} CutOption */
/** @typedef {import("../configurator/configuration.js").SplitOption} SplitOption */

/**
 * @param {Configuration} configuration 
 * @param {Object<GUID,boolean>} ignoreConnectorIds - Object where the keys are connector ids that should be ignored
 * @returns {Array<ExtendOption>}
 */
const findExtendOptions = ( configuration, ignoreConnectorIds = {}) => {

    const connectables = (configuration.blockInstances || []).concat(configuration.connections || []);

    const extendOptions = [];

    if (connectables.length === 0) {

        // empty "stage" => return all blocks

        for (let block of configuration.pkg.blocks || []) {

            extendOptions.push({
                block,
                placement: {
                    position: new Vector3(),
                    quaternion: new Quaternion()
                }
            });
        }
    }
    else {

        for (let connectable of connectables) {

            for (let connector of connectable.connectorTemplate.connectors) {

                // ignore connectors that are on the ignore list
                if ( ignoreConnectorIds[ connector.id ] !== undefined && ignoreConnectorIds[ connector.id ][ connectable.id ] === true ) {
                    continue;
                }

                const connection = (configuration.connections || []).find(cnn =>
                    (cnn.from.connectable === connectable && cnn.from.connector === connector)
                    ||
                    (cnn.to.connectable === connectable && cnn.to.connector === connector)
                );

                if (!connection) {

                    const connectablePlacement = configuration.placement[connectable.id];

                    for (let mateConnector of connector.mate.connectors) {

                        extendOptions.push({

                            block: mateConnector.block,
                            connection: {
                                from: {
                                    connectable,
                                    connector
                                },
                                to: {
                                    connectable: 'new',
                                    connector: mateConnector
                                }
                            },
                            placement: {
                                position: connectablePlacement.position.clone().add(
                                    connector.position.clone().applyQuaternion(connectablePlacement.quaternion)
                                ),
                                quaternion: connector.quaternion.clone().multiply(connectablePlacement.quaternion)
                            }
                        });
                    }
                }
            }
        }
    }

    return extendOptions;
}



/**
 * Find insert options for a configuration
 * (every connection offers a potential insert opportunity)
 * @param {Configuration} configuration
 * @returns {Array<InsertOption>}
 */

const findInsertOptions = configuration => {


    const insertOptions = [];

    for (let connectionToSplit of configuration.connections || []) {

        // all connectors in the package that could possibly 
        // connect to the connector at the from end of this connector

        const fromMates = connectionToSplit.from.connector.mate.connectors || [];
        const toMates = connectionToSplit.to.connector.mate.connectors || [];

        // Every potential mate for the current from-connector sits
        // on a block. To identify the insert options, find the
        // the other connectors on the same block that can mate with 
        // the current to-connector.

        for (let fromMate of fromMates) {

            const otherConnectorsOnBlock = fromMate.block.connectors.filter(con => con !== fromMate);

            const complementingConnectors = otherConnectorsOnBlock.filter(con => toMates.indexOf(con) > -1);

            for (let toMate of complementingConnectors) {

                insertOptions.push({
                    type: 'insert',
                    oldConnection: connectionToSplit,
                    block: fromMate.block,
                    newConnection1: {
                        from: connectionToSplit.from,
                        to: {
                            connectable: 'new',
                            connector: fromMate
                        }
                    },
                    newConnection2: {
                        from: {
                            connectable: 'new',
                            connector: toMate
                        },
                        to: connectionToSplit.to
                    },
                    placement: configuration.placement[connectionToSplit.id]
                });
            }
        }
    }

    return insertOptions;
}




// find delete options
// every block with one connection can be deleted
// as well as blocks with connectable neighbours

// currently not sure how a connection between connections
// could be removed


// PLACEMENT!!!

const findBlockInstanceOptions = configuration => {

    const cutOptions = [];
    const splitOptions = [];

    for (let blockInstance of configuration.blockInstances || []) {

        const eqBICons = blockInstance.equalConnections;

        const splitOption = {
            blockInstance,
            oldConnections: eqBICons,
            siblingChains: blockInstance.siblingChains,
            placement: configuration.placement[blockInstance.id]
        };

        switch (eqBICons.length) {
            case 0:
            case 1:
                const option = {
                    blockInstance,
                    oldConnections: eqBICons,
                    placement: configuration.placement[blockInstance.id]
                };

                cutOptions.push(option);
                break;

            case 2:
                const toUnite = [
                    eqBICons[0].from.connectable !== blockInstance ? eqBICons[0].from : eqBICons[0].to,
                    eqBICons[1].from.connectable !== blockInstance ? eqBICons[1].from : eqBICons[1].to
                ];

                if (toUnite[0].connector.mate.connectors.includes(toUnite[1].connector)) {

                    cutOptions.push({
                        blockInstance,
                        oldConnections: eqBICons,
                        newConnection: {
                            from: toUnite[0],
                            to: toUnite[1]
                        },
                        placement: configuration.placement[blockInstance.id]
                    });
                }

                splitOptions.push(splitOption);
                break;

            default:
                splitOptions.push(splitOption);
                break;
        }
    }

    return { cutOptions, splitOptions };
}


const findReplaceOptions = configuration => {
    const replaceoptions = [];

    for (let blockInstance of configuration.blockInstances || []) {

    }

    return replaceoptions;
}

export {
    findExtendOptions,
    findInsertOptions,
    findBlockInstanceOptions,
    findReplaceOptions,
}