import Operator from '../Operator';
import {isChangeSet} from '../ChangeSet';
import {constant, extend, isFunction} from 'vega-util';

var SKIP = {skip: true};

/**
 * Perform operator updates in response to events. Applies an
 * update function to compute a new operator value. If the update function
 * returns a {@link ChangeSet}, the operator will be pulsed with those tuple
 * changes. Otherwise, the operator value will be updated to the return value.
 * @param {EventStream|Operator} source - The event source to react to.
 *   This argument can be either an EventStream or an Operator.
 * @param {Operator|function(object):Operator} target - The operator to update.
 *   This argument can either be an Operator instance or (if the source
 *   argument is an EventStream), a function that accepts an event object as
 *   input and returns an Operator to target.
 * @param {function(Parameters,Event): *} [update] - Optional update function
 *   to compute the new operator value, or a literal value to set. Update
 *   functions expect to receive a parameter object and event as arguments.
 *   This function can either return a new operator value or (if the source
 *   argument is an EventStream) a {@link ChangeSet} instance to pulse
 *   the target operator with tuple changes.
 * @param {object} [params] - The update function parameters.
 * @param {object} [options] - Additional options hash. If not overridden,
 *   updated operators will be skipped by default.
 * @param {boolean} [options.skip] - If true, the operator will
 *  be skipped: it will not be evaluated, but its dependents will be.
 * @param {boolean} [options.force] - If true, the operator will
 *   be re-evaluated even if its value has not changed.
 * @return {Dataflow}
 */
export default function(source, target, update, params, options) {
  var fn = source instanceof Operator ? onOperator : onStream;
  fn(this, source, target, update, params, options);
  return this;
}

function onStream(df, stream, target, update, params, options) {
  var opt = extend({}, options, SKIP), func, op;

  if (!isFunction(target)) target = constant(target);

  if (update === undefined) {
    func = function(e) {
      df.touch(target(e));
    };
  } else if (isFunction(update)) {
    op = new Operator(null, update, params, false);
    func = function(e) {
      var v, t = target(e);
      op.evaluate(e);
      isChangeSet(v = op.value) ? df.pulse(t, v, options) : df.update(t, v, opt);
    };
  } else {
    func = function(e) {
      df.update(target(e), update, opt);
    };
  }

  stream.apply(func);
}

function onOperator(df, source, target, update, params, options) {
  var func, op;

  if (update === undefined) {
    op = target;
  } else {
    func = isFunction(update) ? update : constant(update);
    update = !target ? func : function(_, pulse) {
      var value = func(_, pulse);
      if (!target.skip()) {
        target.skip(value !== this.value).value = value;
      }
      return value;
    };

    op = new Operator(null, update, params, false);
    op.modified(options && options.force);
    op.rank = source.rank; // immediately follow source

    if (target) {
      op.skip(true); // skip first invocation
      op.value = target.value;
      op.targets().add(target);
      df.connect(target, [op]); // rerank as needed, #1672
    }
  }

  source.targets().add(op);
}

@@ -2163,35 +2163,20 @@
  func = 
-function(e) %7B%0A     
+e =%3E
  df.touc
@@ -2189,23 +2189,16 @@
 et(e));%0A
-    %7D;%0A
   %7D else
@@ -2291,34 +2291,48 @@
 c = 
-function(e) %7B%0A      var v,
+e =%3E %7B%0A      op.evaluate(e);%0A      const
  t =
@@ -2345,30 +2345,22 @@
 t(e)
-;%0A     
+, v =
  op.
-e
 valu
-ate(e)
+e
 ;%0A  
@@ -2376,27 +2376,16 @@
 ngeSet(v
- = op.value
 ) ? df.p
@@ -2461,27 +2461,12 @@
 c = 
-function(e) %7B%0A     
+e =%3E
  df.
@@ -2497,23 +2497,16 @@
 , opt);%0A
-    %7D;%0A
   %7D%0A%0A  s
@@ -2598,25 +2598,8 @@
 ) %7B%0A
-  var func, op;%0A%0A
   if
@@ -2632,297 +2632,86 @@
     
-op = target;%0A  %7D else %7B%0A    func = isFunction(update) ? update : constant(update);%0A    update = !target ? func : function(_, pulse) %7B%0A      var value = func(_, pulse);%0A      if (!target.skip()) %7B%0A        target.skip(value !== this.value).value = value;%0A      %7D%0A      return value;%0A    %7D;%0A%0A
+source.targets().add(target);%0A  %7D else %7B%0A    const opt = options %7C%7C %7B%7D,%0A      
     
@@ -2732,32 +2732,49 @@
 tor(null, update
+r(target, update)
 , params, false)
@@ -2798,23 +2798,8 @@
 (opt
-ions && options
 .for
@@ -2829,16 +2829,22 @@
 ce.rank;
+      
  // imme
@@ -2864,16 +2864,67 @@
 w source
+%0A    source.targets().add(op);    // add dependency
 %0A%0A    if
@@ -2955,16 +2955,28 @@
 p(true);
+            
  // skip
@@ -3019,24 +3019,46 @@
 arget.value;
+   // initialize value
 %0A      op.ta
@@ -3077,16 +3077,39 @@
 target);
+  // chain dependencies
 %0A      d
@@ -3132,16 +3132,17 @@
 , %5Bop%5D);
+ 
  // rera
@@ -3175,35 +3175,329 @@
   %7D%0A
-%0A  source.targets().add(op)
+%7D%0A%0Afunction updater(target, update) %7B%0A  update = isFunction(update) ? update : constant(update);%0A  return target%0A    ? function(_, pulse) %7B%0A        const value = update(_, pulse);%0A        if (!target.skip()) %7B%0A          target.skip(value !== this.value).value = value;%0A        %7D%0A        return value;%0A      %7D%0A    : update
 ;%0A%7D%0A

Streamline signal event handler code.