Source: sources/cores/objects.js

/**
 * @author [Tristan Valcke]{@link https://github.com/Itee}
 * @license [BSD-3-Clause]{@link https://opensource.org/licenses/BSD-3-Clause}
 *
 * @module sources/cores/objects
 * @description Export the utilities methods about objects
 */

import {
    isDefined,
    isNotArray,
    isNotDefined,
    isNotObject,
    isObject,
    isUndefined
} from 'itee-validators'

/**
 *
 * @param {array.<*>} a
 * @returns {array.<*>}
 */
export function uniq ( a ) {
    if ( isNotArray( a ) ) { return }

    const seen = {}
    return a.filter( item => Object.prototype.hasOwnProperty.call( seen, item ) ? false : ( seen[ item ] = true ) )

}

/**
 *
 * @param {object} target
 * @param {object} source
 * @return {object}
 */
export function extend ( target, source ) {

    let output

    if ( isObject( target ) && isNotDefined( source ) ) {

        output = Object.assign( {}, target )

    } else if ( isNotDefined( target ) && isObject( source ) ) {

        output = Object.assign( {}, source )

    } else if ( isObject( target ) && isObject( source ) ) {

        output = Object.assign( {}, target )

        const keys = Object.keys( source )

        for ( let i = 0, n = keys.length ; i < n ; ++i ) {

            let key = keys[ i ]

            if ( isObject( source[ key ] ) ) {

                if ( key in target ) {

                    output[ key ] = extend( target[ key ], source[ key ] )

                } else {

                    Object.assign( output, { [ key ]: source[ key ] } )

                }

            } else {

                Object.assign( output, { [ key ]: source[ key ] } )

            }

        }

    } else {

        output = null

    }

    return output

}

/**
 * Remove old inheritance stuff due to es6 class !
 */
export function serializeObject () {

    //    var object = {}
    //    var a = this.serializeArray()
    //
    //    $.each( a, function () {
    //        if ( object[ this.name ] !== undefined ) {
    //            if ( !object[ this.name ].push ) {
    //                object[ this.name ] = [ object[ this.name ] ]
    //            }
    //            object[ this.name ].push( this.value || '' )
    //        } else {
    //            object[ this.name ] = this.value || ''
    //        }
    //    } )
    //
    //    return object

}

/**
 *
 * @param {class} ChildClass
 * @param {class} ParentClassOrObject
 * @return {*}
 */
export function extendObject ( ChildClass, ParentClassOrObject ) {
    if ( isUndefined( ChildClass ) ) { return }
    if ( isUndefined( ParentClassOrObject ) ) { return }

    if ( ChildClass.constructor === Function && ParentClassOrObject.constructor === Function ) {

        // Normal Inheritance
        ChildClass.prototype             = new ParentClassOrObject()
        ChildClass.prototype.parent      = ParentClassOrObject.prototype
        ChildClass.prototype.constructor = ChildClass

    } else if ( ChildClass.constructor === Function && ParentClassOrObject.constructor === Object ) {

        // Pure Virtual Inheritance
        ChildClass.prototype             = ParentClassOrObject
        ChildClass.prototype.parent      = ParentClassOrObject
        ChildClass.prototype.constructor = ChildClass

    } else if ( ChildClass.constructor === Object && ParentClassOrObject.constructor === Object ) {

        //Object Concatenation Inheritance
        for ( let attribute in ParentClassOrObject ) {

            if ( Object.prototype.hasOwnProperty.call( ChildClass, attribute ) ) { // We are sure that obj[key] belongs to the object and was not inherited.

                if ( ParentClassOrObject[ attribute ].constructor === Object || ParentClassOrObject[ attribute ].constructor === Array ) {

                    ChildClass[ attribute ] = extendObject( ChildClass[ attribute ], ParentClassOrObject[ attribute ] )

                } else {

                    ChildClass[ attribute ] = ParentClassOrObject[ attribute ]

                }

            } else {

                ChildClass[ attribute ] = ParentClassOrObject[ attribute ]

            }

        }

    } else if ( ChildClass.constructor === Array && ParentClassOrObject.constructor === Array ) {

        ChildClass = ChildClass.concat( ParentClassOrObject )

    } else if ( ChildClass.constructor === Object && ParentClassOrObject.constructor === Array ||
        ChildClass.constructor === Array && ParentClassOrObject.constructor === Object ) {

        throw new Error( 'Cannot perform extend of object with an array' )

    } else {

        throw new Error( 'Cannot perform extend given parameters...' )

    }

    return ChildClass

}

/**
 *
 * @param {cloudpoint} particles
 * @param {3dpath} path
 * @param {number} interval
 */
export function createInterval ( particles, path, interval ) {
    if ( !particles ) {return}
    if ( !path ) {return}
    if ( !interval ) {return}

    let globalOffset = 0

    function moveParticlesOnPath() {

        const moveOffset             = 0.1
        const DELTA_BETWEEN_PARTICLE = 1 // meter

        if ( globalOffset >= DELTA_BETWEEN_PARTICLE ) {
            globalOffset = 0
        } else if ( globalOffset + moveOffset > DELTA_BETWEEN_PARTICLE ) { // Avoid final gap jump before new "loop"
            globalOffset = DELTA_BETWEEN_PARTICLE
        } else {
            globalOffset += moveOffset
        }

        const pathLength     = path.getLength()
        let localOffset      = globalOffset
        let normalizedOffset = undefined
        let particle         = undefined
        let newPosition      = undefined

        for ( let i = 0, numberOfParticles = particles.children.length ; i < numberOfParticles ; i++ ) {

            particle         = particles.children[ i ]
            normalizedOffset = localOffset / pathLength

            // End of path ( last particle could go to void, but got an error with getPointAt)
            if ( normalizedOffset > 1 ) {
                normalizedOffset = 0
            }

            newPosition = path.getPointAt( normalizedOffset )
            newPosition.y += 0.1

            particle.position.copy( newPosition )

            localOffset += DELTA_BETWEEN_PARTICLE

        }

    }

    setInterval( moveParticlesOnPath, interval )

}

/**
 *
 * @param {object} enumValues
 * @method toString - return a string representation of the enum
 * @method includes - check if given value is one of the enum
 * @method keys - return an array containing all enum keys
 * @method values - return an array containing all enum values
 * @method entries - return an array containing all enum entries (key -> value)
 *
 * @example {@lang javascript}
 * const Meal = toEnum( {
 *     Food: 'Tartiflette',
 *     Drink: 'Saint-Emilion',
 *     Dessert: 'Mousse au chocolat'
 * } )
 *
 * if( Foo.includes('Tartiflette') ) {
 *     // Happy
 * }
 *
 * const myDrink = 'coke'
 * if( myDrink === Meal.Drink ) {
 *     // Cheers
 * } else {
 *     // Your life is a pain
 * }
 *
 * const MealTypes = Meal.types
 * // ['Tartiflette', 'Saint-Emilion', 'Mousse au chocolat' ]
 */
export function toEnum ( enumValues ) {
    if ( isNotObject( enumValues ) ) { return }
    if ( isDefined( enumValues.toString ) ) {
        const descriptor = Object.getOwnPropertyDescriptor( enumValues, 'toString' )
        if ( isDefined( descriptor ) && descriptor.configurable === false ) {
            return
        }
    }

    return /*#__PURE__*/Object.freeze( /*#__PURE__*/Object.defineProperties( enumValues, {
        toString: {
            configurable: false,
            enumerable:   false,
            writable:     false,
            value () {

                const keys = Object.keys( this )
                let result = ''
                for ( let index = 0, numberOfValues = keys.length ; index < numberOfValues ; index++ ) {
                    result += `${ keys[ index ] }, `
                }
                result = result.slice( 0, -2 )
                return result

            }
        },
        includes: {
            configurable: false,
            enumerable:   false,
            writable:     false,
            value ( key ) {
                return Object.values( this ).includes( key )
            }
        },
        keys: {
            configurable: false,
            enumerable:   false,
            writable:     false,
            value () {
                return Object.keys( this )
            }
        },
        values: {
            configurable: false,
            enumerable:   false,
            writable:     false,
            value () {
                return Object.values( this )
            }
        },
        entries: {
            configurable: false,
            enumerable:   false,
            writable:     false,
            value () {
                return Object.entries( this )
            }
        }
    } ) )

}