reduce.js

import {
  List
} from 'immutable';

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

/**
 * @module reduce
 */

const reduceRecursive = (reduction, self, childPath, reducer, tree, options, nodePath = List()) => {  
  if(isLeaf(self, childPath)) {
    return options.onlyParents ? reduction : reducer(reduction, self, nodePath, tree);
  }
  
  const reducedChildren = self
    .getIn(childPath)
    .reduce((red, kid, key) => reduceRecursive(red, kid, childPath, reducer, tree, options, nodePath.push(key)), reduction);
  
  return options.onlyLeaves
    ? reducedChildren
    : reducer(reducedChildren, self, nodePath, tree);
};

const reduceRecursiveOutwards = (reduction, self, childPath, reducer, tree, options, nodePath = List()) => {
  if(options.onlyParents && isLeaf(self, childPath)) {
    return reduction;
  }
  
  if(isLeaf(self, childPath)) {
    return reducer(reduction, self, nodePath, tree);
  }

  const reducedSelf = options.onlyLeaves
    ? reduction
    : reducer(reduction, self, nodePath, tree);

  return self
    .getIn(childPath)
    .reduce((red, kid, key) => {
      return reduceRecursiveOutwards(red, kid, childPath, reducer, tree, options, nodePath.push(key));
    }, reducedSelf);
};


/**
 * Once fully applied, this iterates through all nodes in the provided tree, passing them all through a `reducer` function.
 * Nodes are processed branch by branch in the order that Immutable maps through the child iterables, inward from leaves to the root node.
 * Sibling nodes are processed in the order that Immutable iterates through collections.
 * 
 * Unlike Immutable.js, if the `initialReduction` is not provided or is null, then the initialReduction will be `null`.
 *
 * @param {Reducer} reducer The function to be called for every node in the tree, the results of which will be used to create the modified tree.
 * @param {ChildPath} [childPath=null] An `Array` or `List` of keys indicating where to find each node's children from within each node.
 * @return {InputFunction} A partially applied function which accepts a single tree `Iterable`, and returns the modified tree `Iterable`.
 */

function deepReduce(reducer, initialReduction = null, childPath = null) {
    return (tree) => reduceRecursive(initialReduction, tree, asList(childPath), reducer, tree, {});
}

/**
 * Once fully applied, this iterates through the leaf nodes in the provided tree, passing them all through a `reducer` 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.
 *
 * Unlike Immutable.js, if the `initialReduction` is not provided or is null, then the initialReduction will be `null`.
 *
 * @param {Reducer} reducer The function to be called for every leaf node in the tree, the results of which will be used to create the modified tree.
 * @param {ChildPath} [childPath=null] An `Array` or `List` of keys indicating where to find each node's children from within each node.
 * @return {InputFunction} A partially applied function which accepts a single tree `Iterable`, and returns the modified tree `Iterable`.
 */

function deepReduceLeaves(reducer, initialReduction = null, childPath = null) {
    return (tree) => reduceRecursive(initialReduction, tree, asList(childPath), reducer, tree, {onlyLeaves: true});
}

/**
 * Once fully applied, this iterates through the parent nodes in the provided tree, passing them all through a `reducer` function. Parent nodes are nodes that have child nodes.
 * Nodes are processed branch by branch in the order that Immutable maps through the child iterables, inward from leaves to the root node.
 *
 * Unlike Immutable.js, if the `initialReduction` is not provided or is null, then the initialReduction will be `null`.
 *
 * @param {Reducer} reducer The function to be called for every parent node in the tree.
 * the results of which will be used to create the modified iterable.
 * @param {ChildPath} [childPath=null] An `Array` or `List` of keys indicating where to find each node's children from within each node.
 * @return {InputFunction} A partially applied function which accepts a single tree `Iterable`, and returns the modified tree `Iterable`.
 */

function deepReduceParents(reducer, initialReduction = null, childPath = null) {
    return (tree) => reduceRecursive(initialReduction, tree, asList(childPath), reducer, tree, {onlyParents: true});
}

/**
 * Once fully applied, this iterates through all nodes in the provided tree, passing them all through a `reducer` function.
 * Nodes are processed branch by branch in the order that Immutable maps through the child iterables, outwards from the root node to the leaf nodes.
 * Sibling nodes are processed in the order that Immutable iterates through collections.
 *
 * Unlike Immutable.js, if the `initialReduction` is not provided or is null, then the initialReduction will be `null`.
 *
 * @param {Reducer} reducer The function to be called for every node in the tree, the results of which will be used to create the modified tree.
 * @param {ChildPath} [childPath=null] An `Array` or `List` of keys indicating where to find each node's children from within each node.
 * @return {InputFunction} A partially applied function which accepts a single tree `Iterable`, and returns the modified tree `Iterable`.
 */

function deepReduceOutwards(reducer, initialReduction = null, childPath = null) {
    return (tree) => reduceRecursiveOutwards(initialReduction, tree, asList(childPath), reducer, tree, {});
}

/**
 * Once fully applied, this iterates through the leaf nodes in the provided tree, passing them all through a `reducer` function.
 * Nodes are processed branch by branch in the order that Immutable maps through the child iterables, outwards from the root node to the leaf nodes.
 * Sibling nodes are processed in the order that Immutable iterates through collections.
 *
 * Because this moves from the root node outwards this function behaves quite differently to the other deep map functions.
 *
 * Unlike Immutable.js, if the `initialReduction` is not provided or is null, then the initialReduction will be `null`.
 *
 * @param {Reducer} reducer The function to be called for every leaf node in the tree, the results of which will be used to create the modified tree.
 * @param {ChildPath} [childPath=null] An `Array` or `List` of keys indicating where to find each node's children from within each node.
 * @return {InputFunction} A partially applied function which accepts a single tree `Iterable`, and returns the modified tree `Iterable`.
 */

function deepReduceLeavesOutwards(reducer, initialReduction = null, childPath = null) {
    return (tree) => reduceRecursiveOutwards(initialReduction, tree, asList(childPath), reducer, tree, {onlyLeaves: true});
}

/**
 * Once fully applied, this iterates through the parent nodes in the provided tree, passing them all through a `reducer` function.
 * Nodes are processed branch by branch in the order that Immutable maps through the child iterables, outwards from the root node to the leaf nodes.
 * Sibling nodes are processed in the order that Immutable iterates through collections.
 *
 * Because this moves from the root node outwards this function behaves quite differently to the other deep map functions.
 *
 * Unlike Immutable.js, if the `initialReduction` is not provided or is null, then the initialReduction will be `null`.
 *
 * @param {Reducer} reducer The function to be called for every parent node in the tree, the results of which will be used to create the modified tree.
 * @param {ChildPath} [childPath=null] An `Array` or `List` of keys indicating where to find each node's children from within each node.
 * @return {InputFunction} A partially applied function which accepts a single tree `Iterable`, and returns the modified tree `Iterable`.
 */

function deepReduceParentsOutwards(reducer, initialReduction = null, childPath = null) {
    return (tree) => reduceRecursiveOutwards(initialReduction, tree, asList(childPath), reducer, tree, {onlyParents: true});
}

export {
  deepReduce,
  deepReduceLeaves,
  deepReduceParents,
  deepReduceOutwards,
  deepReduceLeavesOutwards,
  deepReduceParentsOutwards
}