text: "use strict";
  /*
   * Copyright 2019 gRPC authors.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
   */
  Object.defineProperty(exports, "__esModule", { value: true });
  const load_balancer_1 = require("./load-balancer");
  const channel_1 = require("./channel");
  const resolver_1 = require("./resolver");
  const picker_1 = require("./picker");
  const backoff_timeout_1 = require("./backoff-timeout");
  const constants_1 = require("./constants");
  const metadata_1 = require("./metadata");
  const logging = require("./logging");
  const constants_2 = require("./constants");
  const uri_parser_1 = require("./uri-parser");
  const load_balancer_child_handler_1 = require("./load-balancer-child-handler");
  const TRACER_NAME = 'resolving_load_balancer';
  function trace(text) {
      logging.trace(constants_2.LogVerbosity.DEBUG, TRACER_NAME, text);
  }
  const DEFAULT_LOAD_BALANCER_NAME = 'pick_first';
  class ResolvingLoadBalancer {
      /**
       * Wrapper class that behaves like a `LoadBalancer` and also handles name
       * resolution internally.
       * @param target The address of the backend to connect to.
       * @param channelControlHelper `ChannelControlHelper` instance provided by
       *     this load balancer's owner.
       * @param defaultServiceConfig The default service configuration to be used
       *     if none is provided by the name resolver. A `null` value indicates
       *     that the default behavior should be the default unconfigured behavior.
       *     In practice, that means using the "pick first" load balancer
       *     implmentation
       */
      constructor(target, channelControlHelper, defaultServiceConfig) {
          this.target = target;
          this.channelControlHelper = channelControlHelper;
          this.defaultServiceConfig = defaultServiceConfig;
          this.latestChildState = channel_1.ConnectivityState.IDLE;
          this.latestChildPicker = new picker_1.QueuePicker(this);
          /**
           * This resolving load balancer's current connectivity state.
           */
          this.currentState = channel_1.ConnectivityState.IDLE;
          /**
           * The service config object from the last successful resolution, if
           * available. A value of undefined indicates that there has not yet
           * been a successful resolution. A value of null indicates that the last
           * successful resolution explicitly provided a null service config.
           */
          this.previousServiceConfig = undefined;
          /**
           * Indicates whether we should attempt to resolve again after the backoff
           * timer runs out.
           */
          this.continueResolving = false;
          this.updateState(channel_1.ConnectivityState.IDLE, new picker_1.QueuePicker(this));
          this.childLoadBalancer = new load_balancer_child_handler_1.ChildLoadBalancerHandler({
              createSubchannel: channelControlHelper.createSubchannel.bind(channelControlHelper),
              requestReresolution: () => {
                  /* If the backoffTimeout is running, we're still backing off from
                   * making resolve requests, so we shouldn't make another one here.
                   * In that case, the backoff timer callback will call
                   * updateResolution */
                  if (this.backoffTimeout.isRunning()) {
                      this.continueResolving = true;
                  }
                  else {
                      this.updateResolution();
                  }
              },
              updateState: (newState, picker) => {
                  this.latestChildState = newState;
                  this.latestChildPicker = picker;
                  this.updateState(newState, picker);
              },
          });
          this.innerResolver = resolver_1.createResolver(target, {
              onSuccessfulResolution: (addressList, serviceConfig, serviceConfigError, attributes) => {
                  var _a;
                  let workingServiceConfig = null;
                  /* This first group of conditionals implements the algorithm described
                   * in https://github.com/grpc/proposal/blob/master/A21-service-config-error-handling.md
                   * in the section called "Behavior on receiving a new gRPC Config".
                   */
                  if (serviceConfig === null) {
                      // Step 4 and 5
                      if (serviceConfigError === null) {
                          // Step 5
                          this.previousServiceConfig = serviceConfig;
                          workingServiceConfig = this.defaultServiceConfig;
                      }
                      else {
                          // Step 4
                          if (this.previousServiceConfig === undefined) {
                              // Step 4.ii
                              if (this.defaultServiceConfig === null) {
                                  // Step 4.ii.b
                                  this.handleResolutionFailure(serviceConfigError);
                              }
                              else {
                                  // Step 4.ii.a
                                  workingServiceConfig = this.defaultServiceConfig;
                              }
                          }
                          else {
                              // Step 4.i
                              workingServiceConfig = this.previousServiceConfig;
                          }
                      }
                  }
                  else {
                      // Step 3
                      workingServiceConfig = serviceConfig;
                      this.previousServiceConfig = serviceConfig;
                  }
                  const workingConfigList = (_a = workingServiceConfig === null || workingServiceConfig === void 0 ? void 0 : workingServiceConfig.loadBalancingConfig) !== null && _a !== void 0 ? _a : [];
                  if (workingConfigList.length === 0) {
                      workingConfigList.push({
                          name: 'pick_first',
                          pick_first: {},
                      });
                  }
                  const loadBalancingConfig = load_balancer_1.getFirstUsableConfig(workingConfigList);
                  if (loadBalancingConfig === null) {
                      // There were load balancing configs but none are supported. This counts as a resolution failure
                      this.handleResolutionFailure({
                          code: constants_1.Status.UNAVAILABLE,
                          details: 'All load balancer options in service config are not compatible',
                          metadata: new metadata_1.Metadata(),
                      });
                      return;
                  }
                  this.childLoadBalancer.updateAddressList(addressList, loadBalancingConfig, attributes);
              },
              onError: (error) => {
                  this.handleResolutionFailure(error);
              },
          });
          this.backoffTimeout = new backoff_timeout_1.BackoffTimeout(() => {
              if (this.continueResolving) {
                  this.updateResolution();
                  this.continueResolving = false;
              }
              else {
                  this.updateState(this.latestChildState, this.latestChildPicker);
              }
          });
      }
      updateResolution() {
          this.innerResolver.updateResolution();
          if (this.currentState === channel_1.ConnectivityState.IDLE) {
              this.updateState(channel_1.ConnectivityState.CONNECTING, new picker_1.QueuePicker(this));
          }
      }
      updateState(connectivityState, picker) {
          trace(uri_parser_1.uriToString(this.target) +
              ' ' +
              channel_1.ConnectivityState[this.currentState] +
              ' -> ' +
              channel_1.ConnectivityState[connectivityState]);
          // Ensure that this.exitIdle() is called by the picker
          if (connectivityState === channel_1.ConnectivityState.IDLE) {
              picker = new picker_1.QueuePicker(this);
          }
          this.currentState = connectivityState;
          this.channelControlHelper.updateState(connectivityState, picker);
      }
      handleResolutionFailure(error) {
          if (this.latestChildState === channel_1.ConnectivityState.IDLE) {
              this.updateState(channel_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker(error));
          }
          this.backoffTimeout.runOnce();
      }
      exitIdle() {
          this.childLoadBalancer.exitIdle();
          if (this.currentState === channel_1.ConnectivityState.IDLE) {
              if (this.backoffTimeout.isRunning()) {
                  this.continueResolving = true;
              }
              else {
                  this.updateResolution();
              }
              this.updateState(channel_1.ConnectivityState.CONNECTING, new picker_1.QueuePicker(this));
          }
      }
      updateAddressList(addressList, lbConfig) {
          throw new Error('updateAddressList not supported on ResolvingLoadBalancer');
      }
      resetBackoff() {
          this.backoffTimeout.reset();
          this.childLoadBalancer.resetBackoff();
      }
      destroy() {
          this.childLoadBalancer.destroy();
          this.updateState(channel_1.ConnectivityState.SHUTDOWN, new picker_1.UnavailablePicker());
      }
      getTypeName() {
          return 'resolving_load_balancer';
      }
  }
  exports.ResolvingLoadBalancer = ResolvingLoadBalancer;
  //# sourceMappingURL=resolving-load-balancer.js.map