Source: ReactViews/Custom/CustomComponents.js

'use strict';

import React from 'react';

import AssociativeArray from 'terriajs-cesium/Source/Core/AssociativeArray';

import arraysAreEqual from '../../Core/arraysAreEqual';

const types = new AssociativeArray();

/**
 * A store of registered custom component types, eg. <chart>.
 */
const CustomComponents = {

    register: function(customComponentType) {
        types.set(customComponentType.name, customComponentType);
    },

    // isCustomComponent: function(componentName) {
    //     return (types.contains(componentName.toLowerCase()));
    // },

    names: function() {
        return types.values.map(customComponentType=>customComponentType.name);
    },

    isRegistered: function(name) {
        return this.names().indexOf(name) >= 0;
    },

    attributes: function() {
        const nestedArrays = types.values.map(customComponentType=>customComponentType.attributes);
        // Flatten the array.
        return nestedArrays.reduce(function(a, b) {
            return a.concat(b);
        }, []);
    },

    values: function() {
        return types.values;
    },

    /**
     * Traverses a tree of react components and returns all custom components, based on their "isCorresponding" function.
     * @param  {ReactComponent} tree The tree of react components to traverse.
     * @return {Object[]} An array of the custom components as objects {type: CustomComponentType, reactComponent: ReactComponent}.
     */
    find: findCustomComponentsInTree,

    /**
     * Select the relevant updateCounter from the updateCounters object, by matching against the reactComponent type and key props.
     * Used by potentially self-updating components.
     * updateCounters is passed as context to CustomComponentType's processChartNode function.
     * @param {Object} context.updateCounters Used for self-updating components. An object whose keys are timeoutIds, and whose values are
     *                 {reactComponent: ReactComponent, counter: Integer}. reactComponent is selected by this.isCorresponding.
     * @param  {ReactComponent} reactComponentType Eg. The constructor of Chart.jsx.
     * @param  {Object} keyProps If the values of these props match those of the updateCounters' reactComponent, we declare a match.
     * @return {Integer} The updateCounter for this reactComponent, or undefined if none.
     */
    getUpdateCounter: getUpdateCounter

};

/**
 * @private
 */
function findCustomComponentsInTree(tree) {
    // Similar to logic in React-shallow-test-utils.
    if (!tree) {
        return [];
    }
    let found = [];
    types.values.forEach(customComponentType=>{
        if (customComponentType.isCorresponding(tree)) {
            found = found.concat({type: customComponentType, reactComponent: tree});
        }
    });
    if (React.isValidElement(tree)) {
        if (React.Children.count(tree.props.children) > 0) {
            React.Children.forEach(tree.props.children, function(child) {
                found = found.concat(findCustomComponentsInTree(child));
            });
        }
    }
    return found;
}

/**
 * @private
 */
function getUpdateCounter(updateCounters, reactComponentType, keyProps) {

    /**
     * @private
     */
    function haveTheKeyProps(theseProps) {
        for (const key in keyProps) {
            if (keyProps.hasOwnProperty(key)) {
                if (Array.isArray(keyProps[key])) {
                    if (!arraysAreEqual(theseProps[key], keyProps[key])) {
                        return false;
                    }
                } else if (theseProps[key] !== keyProps[key]) {
                    return false;
                }
            }
        }
        return true;
    }

    if (!updateCounters) {
        return;
    }
    for (const key in updateCounters) {
        if (updateCounters.hasOwnProperty(key)) {
            const updateObject = updateCounters[key];
            const reactComponent = updateObject.reactComponent;
            if (reactComponent.type === reactComponentType) {
                if (haveTheKeyProps(reactComponent.props)) {
                    return updateObject.counter;
                }
            }
        }
    }
}

module.exports = CustomComponents;