"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.Emitter = undefined;

var _index = require("../values/index.js");

var _generator = require("../utils/generator.js");

var _invariant = require("../invariant.js");

var _invariant2 = _interopRequireDefault(_invariant);

var _types = require("./types.js");

var _ResidualFunctions = require("./ResidualFunctions.js");

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

// The emitter keeps track of a stack of what's currently being emitted.
// There are two kinds of interesting dependencies the emitter is dealing with:
// 1. Value dependencies:
//    If an emission task depends on the result of another emission task which
//    is still currently being emitted, then the emission task must be performed later,
//    once the dependency is available.
//    To this end, the emitter maintains the `_activeValues` and `_waitingForValues` datastructures.
// 2. Generator dependencies:
//    For each generator, there's a corresponding "body", i.e. a stream of babel statements
//    that the emitter is appending to.
//    There's always a "current" body that is currently being emitted to.
//    There's also a distinguished `mainBody` to which all statements get directly or indirectly appended.
//    If there are multiple generators/bodies involved, then they form a stack.
//    Nested bodies are usually composed into an instruction emitted to the outer body.
//    For example, two nested generators may yield the then and else-branch of an `if` statement.
//    When an emission is supposed to target a body that is the current body, i.e. when it sits
//    lower on the stack, then the emission task gets delayed until the next emission task on
//    the lower body entry is finished.
//    To this end, the emitter maintains the `_activeGeneratorStack` and `_waitingForBodies` datastructures.


// Type used to configure callbacks from the dependenciesVisitor of the Emitter.
class Emitter {
  constructor(residualFunctions, referencedDeclaredValues) {
    this._mainBody = { type: "MainGenerator", parentBody: undefined, entries: [], done: false };
    this._waitingForValues = new Map();
    this._waitingForBodies = new Map();
    this._body = this._mainBody;
    this._residualFunctions = residualFunctions;
    this._activeStack = [];
    this._activeValues = new Set();
    this._activeGeneratorStack = [this._mainBody];
    this._finalized = false;
    this._getReasonToWaitForDependenciesCallbacks = {
      onActive: val => val, // cyclic dependency; we need to wait until this value has finished emitting
      onFunction: val => {
        // Functions are currently handled in a special way --- they are all defined ahead of time. Thus, we never have to wait for functions.
        this._residualFunctions.addFunctionUsage(val, this.getBodyReference());
        return undefined;
      },
      onAbstractValueWithIdentifier: val => {
        // If the value hasn't been declared yet, then we should wait for it.
        if (!this.cannotDeclare() && !this.hasBeenDeclared(val) && (!this.emittingToAdditionalFunction() || referencedDeclaredValues.get(val) !== undefined)) return val;else return undefined;
      }
    };
  } // Contains all the active generator bodies in stack order.


  // Begin to emit something. Such sessions can be nested.
  // The dependency indicates what is being emitted; until this emission ends, other parties might have to wait for the dependency.
  // The targetBody is a wrapper that holds the sequence of statements that are going to be emitted.
  // If isChild, then we are starting a new emitting session as a branch off the previously active emitting session.
  beginEmitting(dependency, targetBody, isChild = false) {
    (0, _invariant2.default)(!this._finalized);
    this._activeStack.push(dependency);
    if (dependency instanceof _index.Value) {
      (0, _invariant2.default)(!this._activeValues.has(dependency));
      this._activeValues.add(dependency);
    } else if (dependency instanceof _generator.Generator) {
      (0, _invariant2.default)(!this._activeGeneratorStack.includes(targetBody));
      this._activeGeneratorStack.push(targetBody);
    }
    if (isChild) {
      (0, _invariant2.default)(targetBody.type === "Generator" || targetBody.type === "ConditionalAssignmentBranch");
      targetBody.parentBody = this._body;
      targetBody.nestingLevel = (this._body.nestingLevel || 0) + 1;
    }
    let oldBody = this._body;
    this._body = targetBody;
    return oldBody;
  }
  emit(statement) {
    (0, _invariant2.default)(!this._finalized);
    this._body.entries.push(statement);
    this._processCurrentBody();
  }
  // End to emit something. The parameters dependency and isChild must match a previous call to beginEmitting.
  // oldBody should be the value returned by the previous matching beginEmitting call.
  // valuesToProcess is filled with values that have been newly declared since the last corresponding beginEmitting call;
  // other values not yet have been emitted as they might be waiting for valuesToProcess;
  // processValues(valuesToProcess) should be called once the returned body has been embedded in the outer context.
  endEmitting(dependency, oldBody, valuesToProcess, isChild = false) {
    (0, _invariant2.default)(!this._finalized);
    let lastDependency = this._activeStack.pop();
    (0, _invariant2.default)(dependency === lastDependency);
    if (dependency instanceof _index.Value) {
      (0, _invariant2.default)(this._activeValues.has(dependency));
      this._activeValues.delete(dependency);
      this._processValue(dependency);
    } else if (dependency instanceof _generator.Generator) {
      (0, _invariant2.default)(this._isEmittingActiveGenerator());
      this._activeGeneratorStack.pop();
    }
    let lastBody = this._body;
    this._body = oldBody;
    if (isChild) {
      (0, _invariant2.default)(lastBody.parentBody === oldBody);
      (0, _invariant2.default)((lastBody.nestingLevel || 0) > 0);
      (0, _invariant2.default)(!lastBody.done);
      lastBody.done = true;
      // When we are done processing a body, we can propogate all declared abstract values
      // to its parent, possibly unlocking further processing...
      if (lastBody.declaredAbstractValues) {
        let anyPropagated = true;
        for (let b = lastBody; b.done && b.parentBody !== undefined && anyPropagated; b = b.parentBody) {
          anyPropagated = false;
          let parentDeclaredAbstractValues = b.parentBody.declaredAbstractValues;
          if (parentDeclaredAbstractValues === undefined) b.parentBody.declaredAbstractValues = parentDeclaredAbstractValues = new Map();
          (0, _invariant2.default)(b.declaredAbstractValues);
          for (let [key, value] of b.declaredAbstractValues) {
            if (!parentDeclaredAbstractValues.has(key)) {
              parentDeclaredAbstractValues.set(key, value);
              if (valuesToProcess !== undefined) valuesToProcess.add(key);
              anyPropagated = true;
            }
          }
        }
      }
    }

    return lastBody;
  }
  processValues(valuesToProcess) {
    for (let value of valuesToProcess) this._processValue(value);
  }
  finalize() {
    (0, _invariant2.default)(!this._finalized);
    (0, _invariant2.default)(this._activeGeneratorStack.length === 1);
    (0, _invariant2.default)(this._activeGeneratorStack[0] === this._body);
    (0, _invariant2.default)(this._body === this._mainBody);
    this._processCurrentBody();
    this._activeGeneratorStack.pop();
    this._finalized = true;
    (0, _invariant2.default)(this._waitingForBodies.size === 0);
    (0, _invariant2.default)(this._waitingForValues.size === 0);
    (0, _invariant2.default)(this._activeStack.length === 0);
    (0, _invariant2.default)(this._activeValues.size === 0);
    (0, _invariant2.default)(this._activeGeneratorStack.length === 0);
  }
  /**
   * Emitter is emitting in two modes:
   * 1. Emitting to entries in current active generator
   * 2. Emitting to body of another scope(generator or residual function)
   * This function checks the first condition above.
   */
  _isEmittingActiveGenerator() {
    (0, _invariant2.default)(this._activeGeneratorStack.length > 0);
    return this._activeGeneratorStack[this._activeGeneratorStack.length - 1] === this._body;
  }
  _isGeneratorBody(body) {
    return body.type === "MainGenerator" || body.type === "Generator" || body.type === "AdditionalFunction";
  }
  _processCurrentBody() {
    if (!this._isEmittingActiveGenerator()) {
      return;
    }
    let a = this._waitingForBodies.get(this._body);
    if (a === undefined) return;
    while (a.length > 0) {
      let { dependencies, func } = a.shift();
      this.emitNowOrAfterWaitingForDependencies(dependencies, func);
    }
    this._waitingForBodies.delete(this._body);
  }
  _processValue(value) {
    let a = this._waitingForValues.get(value);
    if (a === undefined) return;
    let currentBody = this._body;
    while (a.length > 0) {
      let { body, dependencies, func } = a.shift();
      // If body is not generator body no need to wait for it.
      if (this._isGeneratorBody(body) && body !== currentBody) {
        this._emitAfterWaitingForGeneratorBody(body, dependencies, func);
      } else {
        this.emitNowOrAfterWaitingForDependencies(dependencies, func, body);
      }
    }
    this._waitingForValues.delete(value);
  }

  // Find the first ancestor in input generator body stack that is in current active stack.
  // It can always find one because the bottom one in the stack is the main generator.
  _getFirstAncestorGeneratorWithActiveBody(bodyStack) {
    const activeBody = bodyStack.slice().reverse().find(body => this._activeGeneratorStack.includes(body));
    (0, _invariant2.default)(activeBody);
    return activeBody;
  }

  // Serialization of a statement related to a value MUST be delayed if
  // the creation of the value's identity requires the availability of either:
  // 1. a value that is also currently being serialized
  //    (tracked by `_activeValues`).
  // 2. a time-dependent value that is declared by some generator entry
  //    that has not yet been processed
  //    (tracked by `declaredAbstractValues` in bodies)
  getReasonToWaitForDependencies(dependencies) {
    return this.dependenciesVisitor(dependencies, this._getReasonToWaitForDependenciesCallbacks);
  }

  // Visitor of dependencies that require delaying serialization
  dependenciesVisitor(dependencies, callbacks) {
    (0, _invariant2.default)(!this._finalized);

    let result;
    let recurse = value => this.dependenciesVisitor(value, callbacks);

    if (Array.isArray(dependencies)) {
      let values = dependencies;
      for (let value of values) {
        result = recurse(value);
        if (result !== undefined) return result;
      }
      return undefined;
    }

    let val = dependencies;
    if (this._activeValues.has(val)) {
      // We ran into a cyclic dependency, where the value we are dependending on is still in the process of being emitted.
      result = callbacks.onActive ? callbacks.onActive(val) : undefined;
      if (result !== undefined) return result;
    }

    if (val instanceof _index.BoundFunctionValue) {
      result = recurse(val.$BoundTargetFunction);
      if (result !== undefined) return result;
      result = recurse(val.$BoundThis);
      if (result !== undefined) return result;
      result = recurse(val.$BoundArguments);
      if (result !== undefined) return result;
    } else if (val instanceof _index.FunctionValue) {
      // We ran into a function value.
      result = callbacks.onFunction ? callbacks.onFunction(val) : undefined;
      if (result !== undefined) return result;
    } else if (val instanceof _index.AbstractValue) {
      if (val.hasIdentifier()) {
        // We ran into an abstract value that might have to be declared.
        result = callbacks.onAbstractValueWithIdentifier ? callbacks.onAbstractValueWithIdentifier(val) : undefined;
        if (result !== undefined) return result;
      }
      result = recurse(val.args);
      if (result !== undefined) return result;
    } else if (val instanceof _index.ProxyValue) {
      result = recurse(val.$ProxyTarget);
      if (result !== undefined) return result;
      result = recurse(val.$ProxyHandler);
      if (result !== undefined) return result;
    } else if (val instanceof _index.SymbolValue) {
      if (val.$Description instanceof _index.Value) {
        result = recurse(val.$Description);
        if (result !== undefined) return result;
      }
    } else if (val instanceof _index.ObjectValue) {
      let kind = val.getKind();
      switch (kind) {
        case "Object":
          let proto = val.$Prototype;
          if (proto instanceof _index.ObjectValue) {
            result = recurse(val.$Prototype);
            if (result !== undefined) return result;
          }
          break;
        case "Date":
          (0, _invariant2.default)(val.$DateValue !== undefined);
          result = recurse(val.$DateValue);
          if (result !== undefined) return result;
          break;
        default:
          break;
      }
    }

    return undefined;
  }

  // Wait for a known-to-be active value if a condition is met.
  getReasonToWaitForActiveValue(value, condition) {
    (0, _invariant2.default)(!this._finalized);
    (0, _invariant2.default)(this._activeValues.has(value));
    return condition ? value : undefined;
  }
  _shouldEmitWithoutWaiting(delayReason, targetBody) {
    /**
     * We can directly emit without waiting if:
     * 1. No delayReason
     * 2. delayReason is a generator body while the target body we are not emitting into is not a generator body.
     */
    return !delayReason || !(delayReason instanceof _index.Value) && this._isGeneratorBody(delayReason) && targetBody !== undefined && !this._isGeneratorBody(targetBody);
  }
  emitAfterWaiting(delayReason, dependencies, func, targetBody) {
    if (this._shouldEmitWithoutWaiting(delayReason, targetBody)) {
      if (targetBody === undefined || targetBody === this._body) {
        // Emit into current body.
        func();
      } else {
        (0, _invariant2.default)(!this._isGeneratorBody(targetBody));
        const oldBody = this.beginEmitting(targetBody.type, targetBody);
        func();
        this.endEmitting(targetBody.type, oldBody);
      }
    } else {
      (0, _invariant2.default)(delayReason !== undefined);
      if (delayReason instanceof _index.Value) {
        this._emitAfterWaitingForValue(delayReason, dependencies, targetBody === undefined ? this._body : targetBody, func);
      } else if (this._isGeneratorBody(delayReason)) {
        // delayReason is a generator body.
        this._emitAfterWaitingForGeneratorBody(delayReason, dependencies, func);
      } else {
        // Unknown delay reason.
        (0, _invariant2.default)(false);
      }
    }
  }
  _emitAfterWaitingForValue(reason, dependencies, targetBody, func) {
    (0, _invariant2.default)(!this._finalized);
    (0, _invariant2.default)(!(reason instanceof _index.AbstractValue && this.hasBeenDeclared(reason)) || this._activeValues.has(reason));
    let a = this._waitingForValues.get(reason);
    if (a === undefined) this._waitingForValues.set(reason, a = []);
    a.push({ body: targetBody, dependencies, func });
  }
  _emitAfterWaitingForGeneratorBody(reason, dependencies, func) {
    (0, _invariant2.default)(this._isGeneratorBody(reason));
    (0, _invariant2.default)(!this._finalized);
    (0, _invariant2.default)(this._activeGeneratorStack.includes(reason));
    let b = this._waitingForBodies.get(reason);
    if (b === undefined) this._waitingForBodies.set(reason, b = []);
    b.push({ dependencies, func });
  }
  emitNowOrAfterWaitingForDependencies(dependencies, func, targetBody) {
    (0, _invariant2.default)(!this._finalized);
    this.emitAfterWaiting(this.getReasonToWaitForDependencies(dependencies), dependencies, func, targetBody);
  }
  declare(value) {
    (0, _invariant2.default)(!this._finalized);
    (0, _invariant2.default)(!this._activeValues.has(value));
    (0, _invariant2.default)(value.hasIdentifier());
    (0, _invariant2.default)(this._isEmittingActiveGenerator());
    (0, _invariant2.default)(!this.cannotDeclare());
    (0, _invariant2.default)(!this._body.done);
    if (this._body.declaredAbstractValues === undefined) this._body.declaredAbstractValues = new Map();
    this._body.declaredAbstractValues.set(value, this._body);
    this._processValue(value);
  }
  emittingToAdditionalFunction() {
    // Whether we are directly or indirectly emitting to an additional function
    for (let b = this._body; b !== undefined; b = b.parentBody) if (b.type === "AdditionalFunction") return true;
    return false;
  }
  cannotDeclare() {
    // Bodies of the following types will never contain any (temporal) abstract value declarations.
    return this._body.type === "DelayInitializations" || this._body.type === "LazyObjectInitializer";
  }
  hasBeenDeclared(value) {
    return this.getDeclarationBody(value) !== undefined;
  }
  getDeclarationBody(value) {
    for (let b = this._body; b !== undefined; b = b.parentBody) if (b.declaredAbstractValues !== undefined && b.declaredAbstractValues.has(value)) {
      return b;
    }
    return undefined;
  }
  declaredCount() {
    let declaredAbstractValues = this._body.declaredAbstractValues;
    return declaredAbstractValues === undefined ? 0 : declaredAbstractValues.size;
  }
  getBody() {
    return this._body;
  }
  isCurrentBodyOffspringOf(targetBody) {
    let currentBody = this._body;
    while (currentBody !== undefined) {
      if (currentBody === targetBody) {
        return true;
      }
      currentBody = currentBody.parentBody;
    }
    return false;
  }
  getBodyReference() {
    (0, _invariant2.default)(!this._finalized);
    return new _types.BodyReference(this._body, this._body.entries.length);
  }
}
exports.Emitter = Emitter; /**
                            * Copyright (c) 2017-present, Facebook, Inc.
                            * All rights reserved.
                            *
                            * This source code is licensed under the BSD-style license found in the
                            * LICENSE file in the root directory of this source tree. An additional grant
                            * of patent rights can be found in the PATENTS file in the same directory.
                            */
//# sourceMappingURL=Emitter.js.map