text: "use strict";
  /*
   * Copyright 2016, Google Inc.
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are
   * met:
   *
   *     * Redistributions of source code must retain the above copyright
   * notice, this list of conditions and the following disclaimer.
   *     * Redistributions in binary form must reproduce the above
   * copyright notice, this list of conditions and the following disclaimer
   * in the documentation and/or other materials provided with the
   * distribution.
   *     * Neither the name of Google Inc. nor the names of its
   * contributors may be used to endorse or promote products derived from
   * this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
   * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  Object.defineProperty(exports, "__esModule", { value: true });
  const events_1 = require("events");
  const grpc_1 = require("grpc");
  const apiCallable_1 = require("./apiCallable");
  const gax_1 = require("./gax");
  const GoogleError_1 = require("./GoogleError");
  class LongrunningDescriptor {
      /**
       * Describes the structure of a page-streaming call.
       *
       * @property {OperationsClient} operationsClient
       * @property {anyDecoder} responseDecoder
       * @property {anyDecoder} metadataDecoder
       *
       * @param {OperationsClient} operationsClient - The client used to poll or
       *   cancel an operation.
       * @param {anyDecoder=} responseDecoder - The decoder to unpack
       *   the response message.
       * @param {anyDecoder=} metadataDecoder - The decoder to unpack
       *   the metadata message.
       *
       * @constructor
       */
      constructor(operationsClient, responseDecoder, metadataDecoder) {
          this.operationsClient = operationsClient;
          this.responseDecoder = responseDecoder;
          this.metadataDecoder = metadataDecoder;
      }
      apiCaller() {
          return new LongrunningApiCaller(this);
      }
  }
  exports.LongrunningDescriptor = LongrunningDescriptor;
  class LongrunningApiCaller extends apiCallable_1.NormalApiCaller {
      /**
       * Creates an API caller that performs polling on a long running operation.
       *
       * @private
       * @constructor
       * @param {LongrunningDescriptor} longrunningDescriptor - Holds the
       * decoders used for unpacking responses and the operationsClient
       * used for polling the operation.
       */
      constructor(longrunningDescriptor) {
          super();
          this.longrunningDescriptor = longrunningDescriptor;
      }
      call(apiCall, argument, settings, canceller) {
          canceller.call((argument, callback) => {
              return this._wrapOperation(apiCall, settings, argument, callback);
          }, argument);
      }
      _wrapOperation(apiCall, settings, argument, callback) {
          // TODO: this code defies all logic, and just can't be accurate.
          // tslint:disable-next-line no-any
          let backoffSettings = settings.longrunning;
          if (!backoffSettings) {
              backoffSettings =
                  gax_1.createBackoffSettings(100, 1.3, 60000, null, null, null, null);
          }
          const longrunningDescriptor = this.longrunningDescriptor;
          return apiCall(argument, (err, rawResponse) => {
              if (err) {
                  callback(err, null, rawResponse);
                  return;
              }
              const operation = new Operation(rawResponse, longrunningDescriptor, backoffSettings, settings);
              callback(null, operation, rawResponse);
          });
      }
  }
  exports.LongrunningApiCaller = LongrunningApiCaller;
  class Operation extends events_1.EventEmitter {
      /**
       * Wrapper for a google.longrunnung.Operation.
       *
       * @constructor
       *
       * @param {google.longrunning.Operation} grpcOp - The operation to be wrapped.
       * @param {LongrunningDescriptor} longrunningDescriptor - This defines the
       * operations service client and unpacking mechanisms for the operation.
       * @param {BackoffSettings} backoffSettings - The backoff settings used in
       * in polling the operation.
       * @param {CallOptions=} callOptions - CallOptions used in making get operation
       * requests.
       */
      constructor(grpcOp, longrunningDescriptor, backoffSettings, callOptions) {
          super();
          this.completeListeners = 0;
          this.hasActiveListeners = false;
          this.latestResponse = grpcOp;
          this.longrunningDescriptor = longrunningDescriptor;
          this.result = null;
          this.metadata = null;
          this.backoffSettings = backoffSettings;
          this._unpackResponse(grpcOp);
          this._listenForEvents();
          this._callOptions = callOptions;
      }
      /**
       * Begin listening for events on the operation. This method keeps track of how
       * many "complete" listeners are registered and removed, making sure polling
       * is handled automatically.
       *
       * As long as there is one active "complete" listener, the connection is open.
       * When there are no more listeners, the polling stops.
       *
       * @private
       */
      _listenForEvents() {
          this.on('newListener', event => {
              if (event === 'complete') {
                  this.completeListeners++;
                  if (!this.hasActiveListeners) {
                      this.hasActiveListeners = true;
                      this.startPolling_();
                  }
              }
          });
          this.on('removeListener', event => {
              if (event === 'complete' && --this.completeListeners === 0) {
                  this.hasActiveListeners = false;
              }
          });
      }
      /**
       * Cancels current polling api call and cancels the operation.
       *
       * @return {Promise} the promise of the OperationsClient#cancelOperation api
       * request.
       */
      cancel() {
          if (this.currentCallPromise_) {
              this.currentCallPromise_.cancel();
          }
          const operationsClient = this.longrunningDescriptor.operationsClient;
          return operationsClient.cancelOperation({ name: this.latestResponse.name });
      }
      getOperation(callback) {
          const self = this;
          const operationsClient = this.longrunningDescriptor.operationsClient;
          function promisifyResponse() {
              if (!callback) {
                  // tslint:disable-next-line variable-name
                  const PromiseCtor = self._callOptions.promise;
                  return new PromiseCtor((resolve, reject) => {
                      if (self.latestResponse.error) {
                          const error = new GoogleError_1.GoogleError(self.latestResponse.error.message);
                          error.code = self.latestResponse.error.code;
                          reject(error);
                      }
                      else {
                          resolve([self.result, self.metadata, self.latestResponse]);
                      }
                  });
              }
              return;
          }
          if (this.latestResponse.done) {
              this._unpackResponse(this.latestResponse, callback);
              return promisifyResponse();
          }
          this.currentCallPromise_ = operationsClient.getOperation({ name: this.latestResponse.name }, this._callOptions);
          const noCallbackPromise = this.currentCallPromise_.then(responses => {
              self.latestResponse = responses[0];
              self._unpackResponse(responses[0], callback);
              return promisifyResponse();
          });
          if (!callback) {
              return noCallbackPromise;
          }
      }
      _unpackResponse(op, callback) {
          const responseDecoder = this.longrunningDescriptor.responseDecoder;
          const metadataDecoder = this.longrunningDescriptor.metadataDecoder;
          let response;
          let metadata;
          if (op.done) {
              if (op.result === 'error') {
                  const error = new GoogleError_1.GoogleError(op.error.message);
                  error.code = op.error.code;
                  if (callback) {
                      callback(error);
                  }
                  return;
              }
              if (responseDecoder && op.response) {
                  response = responseDecoder(op.response.value);
                  this.result = response;
              }
          }
          if (metadataDecoder && op.metadata) {
              metadata = metadataDecoder(op.metadata.value);
              this.metadata = metadata;
          }
          if (callback) {
              callback(null, response, metadata, op);
          }
      }
      /**
       * Poll `getOperation` to check the operation's status. This runs a loop to
       * ping using the backoff strategy specified at initialization.
       *
       * Note: This method is automatically called once a "complete" event handler
       * is registered on the operation.
       *
       * @private
       */
      startPolling_() {
          const self = this;
          let now = new Date();
          const delayMult = this.backoffSettings.retryDelayMultiplier;
          const maxDelay = this.backoffSettings.maxRetryDelayMillis;
          let delay = this.backoffSettings.initialRetryDelayMillis;
          let deadline = Infinity;
          if (this.backoffSettings.totalTimeoutMillis) {
              deadline = now.getTime() + this.backoffSettings.totalTimeoutMillis;
          }
          let previousMetadataBytes;
          if (this.latestResponse.metadata) {
              previousMetadataBytes = this.latestResponse.metadata.value;
          }
          // tslint:disable-next-line no-any
          function emit(event, ...args) {
              self.emit(event, ...args);
          }
          function retry() {
              if (!self.hasActiveListeners) {
                  return;
              }
              if (now.getTime() >= deadline) {
                  const error = new GoogleError_1.GoogleError('Total timeout exceeded before any response was received');
                  error.code = grpc_1.status.DEADLINE_EXCEEDED;
                  setImmediate(emit, 'error', error);
                  return;
              }
              self.getOperation((err, result, metadata, rawResponse) => {
                  if (err) {
                      setImmediate(emit, 'error', err);
                      return;
                  }
                  if (!result) {
                      if (rawResponse.metadata &&
                          (!previousMetadataBytes ||
                              !rawResponse.metadata.value.equals(previousMetadataBytes))) {
                          setImmediate(emit, 'progress', metadata, rawResponse);
                          previousMetadataBytes = rawResponse.metadata.value;
                      }
                      // special case: some APIs fail to set either result or error
                      // but set done = true (e.g. speech with silent file).
                      // Don't hang forever in this case.
                      if (rawResponse.done) {
                          const error = new GoogleError_1.GoogleError('Long running operation has finished but there was no result');
                          error.code = grpc_1.status.UNKNOWN;
                          setImmediate(emit, 'error', error);
                          return;
                      }
                      setTimeout(() => {
                          now = new Date();
                          delay = Math.min(delay * delayMult, maxDelay);
                          retry();
                      }, delay);
                      return;
                  }
                  setImmediate(emit, 'complete', result, metadata, rawResponse);
              });
          }
          retry();
      }
      /**
       * Wraps the `complete` and `error` events in a Promise.
       *
       * @return {promise} - Promise that resolves on operation completion and rejects
       * on operation error.
       */
      promise() {
          // tslint:disable-next-line variable-name
          const PromiseCtor = this._callOptions.promise;
          return new PromiseCtor((resolve, reject) => {
              this.on('error', reject)
                  .on('complete', (result, metadata, rawResponse) => {
                  resolve([result, metadata, rawResponse]);
              });
          });
      }
  }
  exports.Operation = Operation;
  /**
   * Method used to create Operation objects.
   *
   * @constructor
   *
   * @param {google.longrunning.Operation} op - The operation to be wrapped.
   * @param {LongrunningDescriptor} longrunningDescriptor - This defines the
   * operations service client and unpacking mechanisms for the operation.
   * @param {BackoffSettings} backoffSettings - The backoff settings used in
   * in polling the operation.
   * @param {CallOptions=} callOptions - CallOptions used in making get operation
   * requests.
   */
  function operation(op, longrunningDescriptor, backoffSettings, callOptions) {
      return new Operation(op, longrunningDescriptor, backoffSettings, callOptions);
  }
  exports.operation = operation;
  //# sourceMappingURL=longrunning.js.map