const promiseQueue = require('./promise-queue');
const curryOptions = require('./curry-options');


const promiseLense = ({parallelism, progress}) => {
  const getChannel = promiseQueue({
    parallelism: Math.max(1, parallelism || 8)
  });

  const factory = (labels = []) => {
    const enqueue = getChannel(labels.join('|'));

    let self = {};
    let total = 0;
    let completed = 0;


    const toString = () =>
      `(${completed}/${total}) ${labels.join(' / ')}`;


    const info = () => ({
      labels: labels.concat(),
      completed,
      total,
      toString
    });


    const create = (label = '*') => {
      total++;
      if (labels.length && progress) {
        progress(info());
      }

      const onComplete = (v) => {
        completed++;
        if (labels.length && progress) {
          progress(info());
        }
        return v;
      };

      const child = (label === '*') ? self : factory(labels.concat(label));

      return {onComplete, child};
    };


    const lens = curryOptions(({from = '*', to = from, label = to}) => {
      const isSelector = (value) =>
        ((typeof value === 'string') && (value !== '*'));

      const getter = (hash) =>
        (isSelector(from) ? hash[from] : hash);

      const setter = (existing) => (value) =>
        ((typeof value === 'undefined') ?
          existing :
          Object.assign({}, existing, isSelector(to) ? {[to]: value} : value));

      return (fn) => (args, context = self) => {
        const {onComplete, child} = context.create(label);

        return Promise.resolve(getter(args))
          .then((v) => fn(v, child))
          .then(setter(args))
          .then(onComplete);
      };
    });


    const all = (list, fn) =>
      Promise.all(list.map((v) =>
        enqueue(() => fn(v))
          .then(create().onComplete)
      ));


    const map = (fn) => (list, context = self) =>
      context.all(list, (v) => fn(v, context));


    const merge = (...fns) => (args, context = self) => {
      const reduceObject = (reduced1, obj = {}, i) => Object.keys(obj)
        .filter((k) => (i === 0) || ((obj[k] !== args[k]) && (obj[k] !== reduced1[k])))
        .reduce((reduced2, k) => Object.assign(reduced2, {[k]: obj[k]}), reduced1);

      return context.all(fns, (fn) => fn(args, context))
        .then((results) => [args].concat(results).reduce(reduceObject, {}));
    };


    self = {create, lens, merge, map, all, toString, info};
    return self;
  };

  return factory();
};

module.exports = promiseLense;

@@ -2010,28 +2010,28 @@
   const 
-reduc
+merg
 eObject
+s
  = (redu
@@ -2048,15 +2048,20 @@
 = %7B%7D
-, i
 ) =%3E
+%0A       
  Obj
@@ -2082,16 +2082,18 @@
         
+  
 .filter(
@@ -2103,22 +2103,8 @@
  =%3E 
-(i === 0) %7C%7C (
 (obj
@@ -2148,18 +2148,19 @@
 ed1%5Bk%5D))
-)
 %0A
+  
         
@@ -2213,19 +2213,117 @@
 2, %7B
-%5Bk%5D: obj%5Bk%5D
+%0A            %5Bk%5D: %5Breduced2%5Bk%5D, obj%5Bk%5D%5D.every(Array.isArray) ? reduced2%5Bk%5D.concat(obj%5Bk%5D) : obj%5Bk%5D%0A          
 %7D), 
@@ -2422,38 +2422,23 @@
  =%3E 
-%5Bargs%5D.concat(
 results
-)
 .reduce(
 redu
@@ -2437,24 +2437,83 @@
 uce(
-reduceObject, %7B%7D
+mergeObjects, %7B%7D))%0A        .then((merged) =%3E Object.assign(%7B%7D, args, merged
 ));%0A

fix merge of arrays