text: import Check from "../Core/Check.js";
  import defaultValue from "../Core/defaultValue.js";
  import defined from "../Core/defined.js";
  import when from "../ThirdParty/when.js";
  import parseFeatureMetadata from "./parseFeatureMetadata.js";
  import ResourceCache from "./ResourceCache.js";
  import ResourceLoader from "./ResourceLoader.js";
  import ResourceLoaderState from "./ResourceLoaderState.js";
  
  /**
   * Loads glTF feature metadata.
   * <p>
   * Implements the {@link ResourceLoader} interface.
   * </p>
   *
   * @alias GltfFeatureMetadataLoader
   * @constructor
   * @augments ResourceLoader
   *
   * @param {Object} options Object with the following properties:
   * @param {Object} options.gltf The glTF JSON.
   * @param {String} options.extension The feature metadata extension object.
   * @param {Resource} options.gltfResource The {@link Resource} containing the glTF.
   * @param {Resource} options.baseResource The {@link Resource} that paths in the glTF JSON are relative to.
   * @param {SupportedImageFormats} options.supportedImageFormats The supported image formats.
   * @param {String} [options.cacheKey] The cache key of the resource.
   * @param {Boolean} [options.asynchronous=true] Determines if WebGL resource creation will be spread out over several frames or block until all WebGL resources are created.
   *
   * @private
   * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
   */
  export default function GltfFeatureMetadataLoader(options) {
    options = defaultValue(options, defaultValue.EMPTY_OBJECT);
    var gltf = options.gltf;
    var extension = options.extension;
    var gltfResource = options.gltfResource;
    var baseResource = options.baseResource;
    var supportedImageFormats = options.supportedImageFormats;
    var cacheKey = options.cacheKey;
    var asynchronous = defaultValue(options.asynchronous, true);
  
    //>>includeStart('debug', pragmas.debug);
    Check.typeOf.object("options.gltf", gltf);
    Check.typeOf.object("options.extension", extension);
    Check.typeOf.object("options.gltfResource", gltfResource);
    Check.typeOf.object("options.baseResource", baseResource);
    Check.typeOf.object("options.supportedImageFormats", supportedImageFormats);
    //>>includeEnd('debug');
  
    this._gltfResource = gltfResource;
    this._baseResource = baseResource;
    this._gltf = gltf;
    this._extension = extension;
    this._supportedImageFormats = supportedImageFormats;
    this._cacheKey = cacheKey;
    this._asynchronous = asynchronous;
    this._bufferViewLoaders = [];
    this._textureLoaders = [];
    this._schemaLoader = undefined;
    this._featureMetadata = undefined;
    this._state = ResourceLoaderState.UNLOADED;
    this._promise = when.defer();
  }
  
  if (defined(Object.create)) {
    GltfFeatureMetadataLoader.prototype = Object.create(ResourceLoader.prototype);
    GltfFeatureMetadataLoader.prototype.constructor = GltfFeatureMetadataLoader;
  }
  
  Object.defineProperties(GltfFeatureMetadataLoader.prototype, {
    /**
     * A promise that resolves to the resource when the resource is ready.
     *
     * @memberof GltfFeatureMetadataLoader.prototype
     *
     * @type {Promise.<GltfFeatureMetadataLoader>}
     * @readonly
     * @private
     */
    promise: {
      get: function () {
        return this._promise.promise;
      },
    },
    /**
     * The cache key of the resource.
     *
     * @memberof GltfFeatureMetadataLoader.prototype
     *
     * @type {String}
     * @readonly
     * @private
     */
    cacheKey: {
      get: function () {
        return this._cacheKey;
      },
    },
    /**
     * Feature metadata.
     *
     * @memberof GltfFeatureMetadataLoader.prototype
     *
     * @type {FeatureMetadata}
     * @readonly
     * @private
     */
    featureMetadata: {
      get: function () {
        return this._featureMetadata;
      },
    },
  });
  
  /**
   * Loads the resource.
   * @private
   */
  GltfFeatureMetadataLoader.prototype.load = function () {
    var bufferViewsPromise = loadBufferViews(this);
    var texturesPromise = loadTextures(this);
    var schemaPromise = loadSchema(this);
  
    this._gltf = undefined; // No longer need to hold onto the glTF
    this._state = ResourceLoaderState.LOADING;
  
    var that = this;
  
    when
      .all([bufferViewsPromise, texturesPromise, schemaPromise])
      .then(function (results) {
        if (that.isDestroyed()) {
          return;
        }
        var bufferViews = results[0];
        var textures = results[1];
        var schema = results[2];
  
        that._featureMetadata = parseFeatureMetadata({
          extension: that._extension,
          schema: schema,
          bufferViews: bufferViews,
          textures: textures,
        });
        that._state = ResourceLoaderState.READY;
        that._promise.resolve(that);
      })
      .otherwise(function (error) {
        if (that.isDestroyed()) {
          return;
        }
        that.unload();
        that._state = ResourceLoaderState.FAILED;
        var errorMessage = "Failed to load feature metadata";
        that._promise.reject(that.getError(errorMessage, error));
      });
  };
  
  function loadBufferViews(featureMetadataLoader) {
    var extension = featureMetadataLoader._extension;
    var featureTables = extension.featureTables;
  
    // Gather the used buffer views
    var bufferViewIds = {};
    if (defined(featureTables)) {
      for (var featureTableId in featureTables) {
        if (featureTables.hasOwnProperty(featureTableId)) {
          var featureTable = featureTables[featureTableId];
          var properties = featureTable.properties;
          if (defined(properties)) {
            for (var propertyId in properties) {
              if (properties.hasOwnProperty(propertyId)) {
                var property = properties[propertyId];
                var bufferView = property.bufferView;
                var arrayOffsetBufferView = property.arrayOffsetBufferView;
                var stringOffsetBufferView = property.stringOffsetBufferView;
                if (defined(bufferView)) {
                  bufferViewIds[bufferView] = true;
                }
                if (defined(arrayOffsetBufferView)) {
                  bufferViewIds[arrayOffsetBufferView] = true;
                }
                if (defined(stringOffsetBufferView)) {
                  bufferViewIds[stringOffsetBufferView] = true;
                }
              }
            }
          }
        }
      }
    }
  
    // Load the buffer views
    var bufferViewPromises = [];
    var bufferViewLoaders = {};
    for (var bufferViewId in bufferViewIds) {
      if (bufferViewIds.hasOwnProperty(bufferViewId)) {
        var bufferViewLoader = ResourceCache.loadBufferView({
          gltf: featureMetadataLoader._gltf,
          bufferViewId: parseInt(bufferViewId),
          gltfResource: featureMetadataLoader._gltfResource,
          baseResource: featureMetadataLoader._baseResource,
        });
        bufferViewPromises.push(bufferViewLoader.promise);
        featureMetadataLoader._bufferViewLoaders.push(bufferViewLoader);
        bufferViewLoaders[bufferViewId] = bufferViewLoader;
      }
    }
  
    // Return a promise to a map of buffer view IDs to typed arrays
    return when.all(bufferViewPromises).then(function () {
      var bufferViews = {};
      for (var bufferViewId in bufferViewLoaders) {
        if (bufferViewLoaders.hasOwnProperty(bufferViewId)) {
          var bufferViewLoader = bufferViewLoaders[bufferViewId];
          // Copy the typed array and let the underlying ArrayBuffer be freed
          var bufferViewTypedArray = new Uint8Array(bufferViewLoader.typedArray);
          bufferViews[bufferViewId] = bufferViewTypedArray;
        }
      }
  
      // Buffer views can be unloaded after the data has been copied
      unloadBufferViews(featureMetadataLoader);
  
      return bufferViews;
    });
  }
  
  function loadTextures(featureMetadataLoader) {
    var extension = featureMetadataLoader._extension;
    var featureTextures = extension.featureTextures;
  
    var gltf = featureMetadataLoader._gltf;
    var gltfResource = featureMetadataLoader._gltfResource;
    var baseResource = featureMetadataLoader._baseResource;
    var supportedImageFormats = featureMetadataLoader._supportedImageFormats;
    var asynchronous = featureMetadataLoader._asynchronous;
  
    // Gather the used textures
    var textureIds = {};
    if (defined(featureTextures)) {
      for (var featureTextureId in featureTextures) {
        if (featureTextures.hasOwnProperty(featureTextureId)) {
          var featureTexture = featureTextures[featureTextureId];
          var properties = featureTexture.properties;
          if (defined(properties)) {
            for (var propertyId in properties) {
              if (properties.hasOwnProperty(propertyId)) {
                var property = properties[propertyId];
                var textureInfo = property.texture;
                textureIds[textureInfo.index] = textureInfo;
              }
            }
          }
        }
      }
    }
  
    // Load the textures
    var texturePromises = [];
    var textureLoaders = {};
    for (var textureId in textureIds) {
      if (textureIds.hasOwnProperty(textureId)) {
        var textureLoader = ResourceCache.loadTexture({
          gltf: gltf,
          textureInfo: textureIds[textureId],
          gltfResource: gltfResource,
          baseResource: baseResource,
          supportedImageFormats: supportedImageFormats,
          asynchronous: asynchronous,
        });
        texturePromises.push(textureLoader.promise);
        featureMetadataLoader._textureLoaders.push(textureLoader);
        textureLoaders[textureId] = textureLoader;
      }
    }
  
    // Return a promise to a map of texture IDs to Texture objects
    return when.all(texturePromises).then(function () {
      var textures = {};
      for (var textureId in textureLoaders) {
        if (textureLoaders.hasOwnProperty(textureId)) {
          var textureLoader = textureLoaders[textureId];
          textures[textureId] = textureLoader.texture;
        }
      }
      return textures;
    });
  }
  
  function loadSchema(featureMetadataLoader) {
    var extension = featureMetadataLoader._extension;
  
    var schemaLoader;
    if (defined(extension.schemaUri)) {
      var resource = featureMetadataLoader._baseResource.getDerivedResource({
        url: extension.schemaUri,
      });
      schemaLoader = ResourceCache.loadSchema({
        resource: resource,
      });
    } else {
      schemaLoader = ResourceCache.loadSchema({
        schema: extension.schema,
      });
    }
  
    featureMetadataLoader._schemaLoader = schemaLoader;
  
    return schemaLoader.promise.then(function (schemaLoader) {
      return schemaLoader.schema;
    });
  }
  
  /**
   * Processes the resource until it becomes ready.
   *
   * @param {FrameState} frameState The frame state.
   * @private
   */
  GltfFeatureMetadataLoader.prototype.process = function (frameState) {
    //>>includeStart('debug', pragmas.debug);
    Check.typeOf.object("frameState", frameState);
    //>>includeEnd('debug');
  
    if (this._state !== ResourceLoaderState.LOADING) {
      return;
    }
  
    var textureLoaders = this._textureLoaders;
    var textureLoadersLength = textureLoaders.length;
  
    for (var i = 0; i < textureLoadersLength; ++i) {
      var textureLoader = textureLoaders[i];
      textureLoader.process(frameState);
    }
  };
  
  function unloadBufferViews(featureMetadataLoader) {
    var bufferViewLoaders = featureMetadataLoader._bufferViewLoaders;
    var bufferViewLoadersLength = bufferViewLoaders.length;
    for (var i = 0; i < bufferViewLoadersLength; ++i) {
      ResourceCache.unload(bufferViewLoaders[i]);
    }
    featureMetadataLoader._bufferViewLoaders.length = 0;
  }
  
  function unloadTextures(featureMetadataLoader) {
    var textureLoaders = featureMetadataLoader._textureLoaders;
    var textureLoadersLength = textureLoaders.length;
    for (var i = 0; i < textureLoadersLength; ++i) {
      ResourceCache.unload(textureLoaders[i]);
    }
    featureMetadataLoader._textureLoaders.length = 0;
  }
  
  /**
   * Unloads the resource.
   * @private
   */
  GltfFeatureMetadataLoader.prototype.unload = function () {
    unloadBufferViews(this);
    unloadTextures(this);
  
    if (defined(this._schemaLoader)) {
      ResourceCache.unload(this._schemaLoader);
    }
    this._schemaLoader = undefined;
  
    this._featureMetadata = undefined;
  };
  