map.js

import {
  List
} from 'immutable';

import {
	isLeaf,
	asList
} from './utils';

/**
 * @module map
 */

 /**
 * @callback inputFunction
 * @param {Iterable} tree The tree Iterable to be processed by one of the deep functions.
 * @return {Iterable} The modified iterable.
 */

 /**
 * @callback mapper
 * @param {*} value The value of the current node
 * @param {List} keys A List of all the keys to the current node
 * @param {List|null} children A list of the current node's children
 * @return {*} The replacement value.
 */

const mapRecursive = (self, childPath, mapper, options, keys = List()) => {
  const mapSelf = (self, children) => mapper(self, keys, children);
  
  if(isLeaf(self, childPath)) {
    return options.onlyParents ? self : mapSelf(self, List());
  }
  
  const mappedChildren = self
    .getIn(childPath)
    .map((kid, key) => mapRecursive(kid, childPath, mapper, options, keys.push(key)));
  
  const selfWithMappedChildren = childPath.isEmpty()
    ? self.merge(mappedChildren)
    : self.setIn(childPath, mappedChildren);
  
  return options.onlyLeaves
    ? selfWithMappedChildren
    : mapSelf(selfWithMappedChildren, mappedChildren);
};


/**
 * Maps through all nodes in the provided tree, passing them all through a mapper function.
 * Nodes are processed inward from leaves to the root node, branch by branch, in the order that Immutable maps through the child iterables.
 * Sibling nodes are processed in the order that Immutable.js iterates through collections.
 *
 * TODO: note about care with child collections, note about using as reduce
 * 
 * @param {mapper} mapper The function to be called for every node in the tree,
 * the results of which will be used to create the modified iterable.
 * @param {Array|List} [childPath=null] An {Array} or {List} of the key path to each node's children.
 * @return {inputFunction} A partially applied function which accepts a single tree interable, and returns the modified iterable.
 */

function deepMap(mapper, childPath = []) {
    return (tree) => mapRecursive(tree, asList(childPath), mapper, {});
}

/**
 * Maps through the leaf nodes in the provided tree, passing them all through a mapper function. Leaf nodes are nodes that have no child nodes.
 * Nodes are processed branch by branch, in the order that Immutable maps through the child iterables.
 *
 * @param {mapper} mapper The function to be called for every node in the tree.
 * the results of which will be used to create the modified iterable.
 * @param {Array|List} [childPath=null] An {Array} or {List} of the key path to each node's children.
 * @return {inputFunction} A partially applied function which accepts a single tree interable, and returns the modified iterable.
 */

function deepMapLeaves(mapper, childPath = []) {
    return (tree) => mapRecursive(tree, asList(childPath), mapper, {onlyLeaves: true});
}

/**
 * Maps through the parent nodes in the provided tree, passing them all through a mapper function. Parent nodes are nodes that have child nodes.
 * Nodes are processed inward from leaves to the root node, branch by branch, in the order that Immutable maps through the child iterables.
 *
 * @param {mapper} mapper The function to be called for every node in the tree.
 * the results of which will be used to create the modified iterable.
 * @param {Array|List} [childPath=null] An {Array} or {List} of the key path to each node's children.
 * @return {inputFunction} A partially applied function which accepts a single tree interable, and returns the modified iterable.
 */

function deepMapParents(mapper, childPath = []) {
    return (tree) => mapRecursive(tree, asList(childPath), mapper, {onlyParents: true});
}

export {
  deepMap,
  deepMapLeaves,
  deepMapParents
}