Source: lib.js

// import { ConnectionType } from "./package/connector/connection_type.js";
// import { Connector } from "./package/connector/connector.js";
// import { ConnectorType } from "./package/connector/connector_type.js";


/**
 * @param {number} waitSecs
 */

export const pause = waitSecs => new Promise(res => setTimeout(res, waitSecs * 1000));


/**
 * @param {Array<number>} nrs
 * @returns {number}
 */

export const sum = nrs => nrs.reduce((a, b) => a + b, 0);


export const eqArr = (arr1, arr2) => {
    // compare lengths - can save a lot of time 

    if (arr1.length !== arr2.length) {
        return false;
    }

    for (const i = 0, l=arr1.length; i < l; i++) {
        // Check if we have nested arrays
        if (arr1[i] instanceof Array && arr2[i] instanceof Array) {
            // recurse into the nested arrays
            if (!eqArr( arr1[i], arr2[i])) {
                return false;
            }
        }           
        else if (arr1[i] !== arr2[i]) { 
            // Warning - two different object instances will never be equal: {x:20} != {x:20}
            return false;
        }           
    }       
    return true;
}


/**
 * @param {Array<String>} keys 
 * @param {Object} obj 
 */
export const omit = (keys, obj) =>
    keys.reduce(
        (a, e) => {
            const { [e]: omit, ...rest } = a;
            return rest;
        },
        obj
    )

/**
 * @param {Array<String>} keys 
 * @param {Object} obj 
 */
export const pick = (keys, obj) =>
    keys.reduce(
        (newObj, k) =>
            k in obj ? Object.assign(newObj, { [k]: obj[k] }) : newObj
        , {}
    );



export const unique = (value, index, self) =>
    self.indexOf(value) === index


export const makeImage = ( width, height, src ) => {
    const img = new Image(128,128);
    img.src = src;
    return img
};

/**
 * @param {Object} instructions
 * @param {Object} instructions.from - source object
 * @param {Object} instructions.into - target object (is modified)
 * @param {Array<string>} [instructions.required]
 * @param {Array<string>} [instructions.optional]
 */

export const copyProps = function ({ from, /* out */ into, required = [], optional = [] }) {

    const copiedProps = [];

    for (let prop of required || []) {
        if (from[prop] === undefined) {
            throw new Error(`Missing property "${prop}"`)
        }

        into[prop] = from[prop];

        copiedProps.push(prop);
    }

    for (let prop of optional || []) {
        if (from[prop] !== undefined) {
            into[prop] = from[prop];
            copiedProps.push(prop);
        }
    }

    return copiedProps;
}

// Safari doesn't understand lookahead

// export const UUIDRegex = new RegExp(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
// export const semverRegex = new RegExp(/(?<=^v?|\sv?)(?:(?:0|[1-9]\d*)\.){2}(?:0|[1-9]\d*)(?:-(?:0|[1-9]\d*|[\da-z-]*[a-z-][\da-z-]*)(?:\.(?:0|[1-9]\d*|[\da-z-]*[a-z-][\da-z-]*))*)?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?\b/gi);

export const UUIDRegex = new RegExp(/^(?:\{[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\}|[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12})$/i);

export const URLRegex = new RegExp(/[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)?/gi);

export const semverRegex = new RegExp(/ ^([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?$\b/gi);


export const getAssigningComponent = obj => obj.positionedMesh || obj.positionedMeshGroup || obj.blockInstance;


/**
 * @link https://stackoverflow.com/questions/40922531/how-to-check-if-a-javascript-function-is-a-constructor
 * @param {Object} obj 
 */
function isConstructor(obj) {
    return !!obj.prototype && !!obj.prototype.constructor.name;
}


/**
 * @param {Object} objToCheck - object to check
 * @param {Object} requiredProps
 * @param {Object} optionalProps

 */

export const checkPropTypes = function (objToCheck, requiredProps = {}, optionalProps = {}) {

    const check = (prop, val, shouldBe) => {

        let errMsg = null;
        let os;

        if (typeof shouldBe === 'object' && shouldBe.msg) { // check for .test interferes with the regex types
            os = shouldBe;
            errMsg = shouldBe.msg || null;
            shouldBe = shouldBe.test;
            // dirty? yes
        }

        if (typeof (shouldBe) === 'string') {
            if (typeof (val) !== shouldBe) {
                throw new Error(`Property [${prop}] is a ${typeof (val)} not a ${shouldBe}. ${errMsg ? errMsg : ''}`);
            }
        }
        else if (shouldBe instanceof RegExp) {
            if (!val.match(shouldBe)) {
                throw new Error(`Property [${prop}] format is incorrect. ${errMsg ? errMsg : ''}`);
            }
        }
        else if (typeof (shouldBe) === 'function' && !isConstructor(shouldBe)) {
            // try {
            const checkResult = shouldBe(val);
            if (checkResult !== true) {
                // value "${val}" 
                throw new Error(`Invalid property [${prop}]. ${typeof (checkResult) === 'string' ? checkResult : ''} ${errMsg ? errMsg : 'a ' + errMsg}`);
            }
            // }
            // catch ( err ) {
            //     console.error( err)
            //     console.error( prop, val, shouldBe)
            //     throw err
            // }
        }
        else if (typeof (shouldBe) === 'function') {
            if (!(val instanceof shouldBe)) {
                throw new Error(`Property [${prop}] is a ${val.constructor.name} not a ${shouldBe.name || shouldBe}. ${errMsg ? errMsg : ''}`);
            }
        }
        else {
            console.error(shouldBe);
            throw new Error('Unknown check type ');
        }
    }

    for (let [prop, shouldBe] of Object.entries(requiredProps)) {

        const val = objToCheck[prop];

        if (val === undefined) {
            throw new Error(`Required property [${prop}] is undefined in keys [${Object.keys(objToCheck).join(', ')}]`);
        }

        // console.log( shouldBe )

        if (Array.isArray(shouldBe)) {
            shouldBe.forEach(subSB => check(prop, val, subSB));
        }
        else {
            check(prop, val, shouldBe);
        }

    }

    for (let [prop, shouldBe] of Object.entries(optionalProps)) {

        const val = objToCheck[prop];

        if (val !== undefined) {
            if (Array.isArray(shouldBe)) {
                shouldBe.forEach(subSB => check(prop, val, subSB));
            }
            else {
                check(prop, val, shouldBe);
            }
        }
    }

    return true;
};

/**
 * Builds a map that indexes potential connection mates per connector type
 * @param {Object} connectionComponents
 * @param {Array<ConnectionType>} connectionComponents.connectionTypes
 * @param {Array<ConnectorType>} connectionComponents.connectorTypes
 * @param {Array<Connector>} connectionComponents.connectors
 * @returns {Map<ConnectorType,Object>}
 */

export const buildMateMap = ({ connectionTypes, connectorTypes, connectors }) => {

    // console.log( 'BMM', connectorTypes)

    const map = new Map();

    for (let ctrType of connectorTypes) {

        const ctMap = {};

        ctMap.connectionTypes = connectionTypes.filter(cnt => cnt.settings.connectorTypes.includes(ctrType));
        ctMap.connectorTypes = [];

        for (let relCnnType of ctMap.connectionTypes) {

            // the ConnectionType can specify the same ConnectorType twice,
            // in this case it can connect to itself. This is why .filter
            // cannot be used here, the relCnnType is found in the array
            // and the other value is taken as mate, regardless of its type

            const types = relCnnType.settings.connectorTypes;

            const mateType = types[0] === ctrType ? types[1] : types[0];

            if (ctMap.connectorTypes.indexOf(mateType) === -1) {
                ctMap.connectorTypes.push(mateType);
            }
        }

        ctMap.connectors = connectors.filter(cn =>
            ctMap.connectorTypes.includes(cn.type)
        );

        map.set(ctrType, ctMap);
    }

    return map;
}

export const ucFirst = str =>
    str.charAt(0).toUpperCase() + str.slice(1);