text: import BaseTexture from './BaseTexture';
  import VideoBaseTexture from './VideoBaseTexture';
  import TextureUvs from './TextureUvs';
  import EventEmitter from 'eventemitter3';
  import { Rectangle } from '../math';
  import { TextureCache, getResolutionOfUrl } from '../utils';
  import settings from '../settings';
  
  /**
   * A texture stores the information that represents an image or part of an image. It cannot be added
   * to the display list directly. Instead use it as the texture for a Sprite. If no frame is provided
   * then the whole image is used.
   *
   * You can directly create a texture from an image and then reuse it multiple times like this :
   *
   * ```js
   * let texture = PIXI.Texture.fromImage('assets/image.png');
   * let sprite1 = new PIXI.Sprite(texture);
   * let sprite2 = new PIXI.Sprite(texture);
   * ```
   *
   * Textures made from SVGs, loaded or not, cannot be used before the file finishes processing.
   * You can check for this by checking the sprite's _textureID property.
   * ```js
   * var texture = PIXI.Texture.fromImage('assets/image.svg');
   * var sprite1 = new PIXI.Sprite(texture);
   * //sprite1._textureID should not be undefined if the texture has finished processing the SVG file
   * ```
   * You can use a ticker or rAF to ensure your sprites load the finished textures after processing. See issue #3068.
   *
   * @class
   * @extends EventEmitter
   * @memberof PIXI
   */
  export default class Texture extends EventEmitter
  {
      /**
       * @param {PIXI.BaseTexture} baseTexture - The base texture source to create the texture from
       * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show
       * @param {PIXI.Rectangle} [orig] - The area of original texture
       * @param {PIXI.Rectangle} [trim] - Trimmed rectangle of original texture
       * @param {number} [rotate] - indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8}
       */
      constructor(baseTexture, frame, orig, trim, rotate)
      {
          super();
  
          /**
           * Does this Texture have any frame data assigned to it?
           *
           * @member {boolean}
           */
          this.noFrame = false;
  
          if (!frame)
          {
              this.noFrame = true;
              frame = new Rectangle(0, 0, 1, 1);
          }
  
          if (baseTexture instanceof Texture)
          {
              baseTexture = baseTexture.baseTexture;
          }
  
          /**
           * The base texture that this texture uses.
           *
           * @member {PIXI.BaseTexture}
           */
          this.baseTexture = baseTexture;
  
          /**
           * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering,
           * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases)
           *
           * @member {PIXI.Rectangle}
           */
          this._frame = frame;
  
          /**
           * This is the trimmed area of original texture, before it was put in atlas
           *
           * @member {PIXI.Rectangle}
           */
          this.trim = trim;
  
          /**
           * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered.
           *
           * @member {boolean}
           */
          this.valid = false;
  
          /**
           * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates)
           *
           * @member {boolean}
           */
          this.requiresUpdate = false;
  
          /**
           * The WebGL UV data cache.
           *
           * @member {PIXI.TextureUvs}
           * @private
           */
          this._uvs = null;
  
          /**
           * This is the area of original texture, before it was put in atlas
           *
           * @member {PIXI.Rectangle}
           */
          this.orig = orig || frame;// new Rectangle(0, 0, 1, 1);
  
          this._rotate = Number(rotate || 0);
  
          if (rotate === true)
          {
              // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures
              this._rotate = 2;
          }
          else if (this._rotate % 2 !== 0)
          {
              throw new Error('attempt to use diamond-shaped UVs. If you are sure, set rotation manually');
          }
  
          if (baseTexture.hasLoaded)
          {
              if (this.noFrame)
              {
                  frame = new Rectangle(0, 0, baseTexture.width, baseTexture.height);
  
                  // if there is no frame we should monitor for any base texture changes..
                  baseTexture.on('update', this.onBaseTextureUpdated, this);
              }
              this.frame = frame;
          }
          else
          {
              baseTexture.once('loaded', this.onBaseTextureLoaded, this);
          }
  
          /**
           * Fired when the texture is updated. This happens if the frame or the baseTexture is updated.
           *
           * @event PIXI.Texture#update
           * @protected
           * @param {PIXI.Texture} texture - Instance of texture being updated.
           */
  
          this._updateID = 0;
  
          /**
           * Extra field for extra plugins. May contain clamp settings and some matrices
           * @type {Object}
           */
          this.transform = null;
  
          /**
           * The ids under which this Texture has been added to the texture cache. This is
           * automatically set as long as Texture.addToCache is used, but may not be set if a
           * Texture is added directly to the TextureCache array.
           *
           * @member {string[]}
           */
          this.textureCacheIds = [];
      }
  
      /**
       * Updates this texture on the gpu.
       *
       */
      update()
      {
          this.baseTexture.update();
      }
  
      /**
       * Called when the base texture is loaded
       *
       * @private
       * @param {PIXI.BaseTexture} baseTexture - The base texture.
       */
      onBaseTextureLoaded(baseTexture)
      {
          this._updateID++;
  
          // TODO this code looks confusing.. boo to abusing getters and setters!
          if (this.noFrame)
          {
              this.frame = new Rectangle(0, 0, baseTexture.width, baseTexture.height);
          }
          else
          {
              this.frame = this._frame;
          }
  
          this.baseTexture.on('update', this.onBaseTextureUpdated, this);
          this.emit('update', this);
      }
  
      /**
       * Called when the base texture is updated
       *
       * @private
       * @param {PIXI.BaseTexture} baseTexture - The base texture.
       */
      onBaseTextureUpdated(baseTexture)
      {
          this._updateID++;
  
          this._frame.width = baseTexture.width;
          this._frame.height = baseTexture.height;
  
          this.emit('update', this);
      }
  
      /**
       * Destroys this texture
       *
       * @param {boolean} [destroyBase=false] Whether to destroy the base texture as well
       */
      destroy(destroyBase)
      {
          if (this.baseTexture)
          {
              if (destroyBase)
              {
                  // delete the texture if it exists in the texture cache..
                  // this only needs to be removed if the base texture is actually destroyed too..
                  if (TextureCache[this.baseTexture.imageUrl])
                  {
                      Texture.removeFromCache(this.baseTexture.imageUrl);
                  }
  
                  this.baseTexture.destroy();
              }
  
              this.baseTexture.off('update', this.onBaseTextureUpdated, this);
              this.baseTexture.off('loaded', this.onBaseTextureLoaded, this);
  
              this.baseTexture = null;
          }
  
          this._frame = null;
          this._uvs = null;
          this.trim = null;
          this.orig = null;
  
          this.valid = false;
  
          Texture.removeFromCache(this);
          this.textureCacheIds = null;
      }
  
      /**
       * Creates a new texture object that acts the same as this one.
       *
       * @return {PIXI.Texture} The new texture
       */
      clone()
      {
          return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate);
      }
  
      /**
       * Updates the internal WebGL UV cache.
       *
       * @protected
       */
      _updateUvs()
      {
          if (!this._uvs)
          {
              this._uvs = new TextureUvs();
          }
  
          this._uvs.set(this._frame, this.baseTexture, this.rotate);
  
          this._updateID++;
      }
  
      /**
       * Helper function that creates a Texture object from the given image url.
       * If the image is not in the texture cache it will be  created and loaded.
       *
       * @static
       * @param {string} imageUrl - The image url of the texture
       * @param {boolean} [crossorigin] - Whether requests should be treated as crossorigin
       * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values
       * @param {number} [sourceScale=(auto)] - Scale for the original image, used with SVG images.
       * @return {PIXI.Texture} The newly created texture
       */
      static fromImage(imageUrl, crossorigin, scaleMode, sourceScale)
      {
          let texture = TextureCache[imageUrl];
  
          if (!texture)
          {
              texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode, sourceScale));
              Texture.addToCache(texture, imageUrl);
          }
  
          return texture;
      }
  
      /**
       * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId
       * The frame ids are created when a Texture packer file has been loaded
       *
       * @static
       * @param {string} frameId - The frame Id of the texture in the cache
       * @return {PIXI.Texture} The newly created texture
       */
      static fromFrame(frameId)
      {
          const texture = TextureCache[frameId];
  
          if (!texture)
          {
              throw new Error(`The frameId "${frameId}" does not exist in the texture cache`);
          }
  
          return texture;
      }
  
      /**
       * Helper function that creates a new Texture based on the given canvas element.
       *
       * @static
       * @param {HTMLCanvasElement} canvas - The canvas element source of the texture
       * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values
       * @param {string} [origin='canvas'] - A string origin of who created the base texture
       * @return {PIXI.Texture} The newly created texture
       */
      static fromCanvas(canvas, scaleMode, origin = 'canvas')
      {
          return new Texture(BaseTexture.fromCanvas(canvas, scaleMode, origin));
      }
  
      /**
       * Helper function that creates a new Texture based on the given video element.
       *
       * @static
       * @param {HTMLVideoElement|string} video - The URL or actual element of the video
       * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values
       * @return {PIXI.Texture} The newly created texture
       */
      static fromVideo(video, scaleMode)
      {
          if (typeof video === 'string')
          {
              return Texture.fromVideoUrl(video, scaleMode);
          }
  
          return new Texture(VideoBaseTexture.fromVideo(video, scaleMode));
      }
  
      /**
       * Helper function that creates a new Texture based on the video url.
       *
       * @static
       * @param {string} videoUrl - URL of the video
       * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values
       * @return {PIXI.Texture} The newly created texture
       */
      static fromVideoUrl(videoUrl, scaleMode)
      {
          return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode));
      }
  
      /**
       * Helper function that creates a new Texture based on the source you provide.
       * The source can be - frame id, image url, video url, canvas element, video element, base texture
       *
       * @static
       * @param {number|string|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement|PIXI.BaseTexture}
       *        source - Source to create texture from
       * @return {PIXI.Texture} The newly created texture
       */
      static from(source)
      {
          // TODO auto detect cross origin..
          // TODO pass in scale mode?
          if (typeof source === 'string')
          {
              const texture = TextureCache[source];
  
              if (!texture)
              {
                  // check if its a video..
                  const isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null;
  
                  if (isVideo)
                  {
                      return Texture.fromVideoUrl(source);
                  }
  
                  return Texture.fromImage(source);
              }
  
              return texture;
          }
          else if (source instanceof HTMLImageElement)
          {
              return new Texture(BaseTexture.from(source));
          }
          else if (source instanceof HTMLCanvasElement)
          {
              return Texture.fromCanvas(source, settings.SCALE_MODE, 'HTMLCanvasElement');
          }
          else if (source instanceof HTMLVideoElement)
          {
              return Texture.fromVideo(source);
          }
          else if (source instanceof BaseTexture)
          {
              return new Texture(source);
          }
  
          // lets assume its a texture!
          return source;
      }
  
      /**
       * Create a texture from a source and add to the cache.
       *
       * @static
       * @param {HTMLImageElement|HTMLCanvasElement} source - The input source.
       * @param {String} imageUrl - File name of texture, for cache and resolving resolution.
       * @param {String} [name] - Human readible name for the texture cache. If no name is
       *        specified, only `imageUrl` will be used as the cache ID.
       * @return {PIXI.Texture} Output texture
       */
      static fromLoader(source, imageUrl, name)
      {
          const baseTexture = new BaseTexture(source, undefined, getResolutionOfUrl(imageUrl));
          const texture = new Texture(baseTexture);
  
          baseTexture.imageUrl = imageUrl;
  
          // No name, use imageUrl instead
          if (!name)
          {
              name = imageUrl;
          }
  
          // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions
          BaseTexture.addToCache(texture.baseTexture, name);
          Texture.addToCache(texture, name);
  
          // also add references by url if they are different.
          if (name !== imageUrl)
          {
              BaseTexture.addToCache(texture.baseTexture, imageUrl);
              Texture.addToCache(texture, imageUrl);
          }
  
          return texture;
      }
  
      /**
       * Adds a Texture to the global TextureCache. This cache is shared across the whole PIXI object.
       *
       * @static
       * @param {PIXI.Texture} texture - The Texture to add to the cache.
       * @param {string} id - The id that the Texture will be stored against.
       */
      static addToCache(texture, id)
      {
          if (id)
          {
              if (texture.textureCacheIds.indexOf(id) === -1)
              {
                  texture.textureCacheIds.push(id);
              }
  
              // @if DEBUG
              /* eslint-disable no-console */
              if (TextureCache[id])
              {
                  console.warn(`Texture added to the cache with an id [${id}] that already had an entry`);
              }
              /* eslint-enable no-console */
              // @endif
  
              TextureCache[id] = texture;
          }
      }
  
      /**
       * Remove a Texture from the global TextureCache.
       *
       * @static
       * @param {string|PIXI.Texture} texture - id of a Texture to be removed, or a Texture instance itself
       * @return {PIXI.Texture|null} The Texture that was removed
       */
      static removeFromCache(texture)
      {
          if (typeof texture === 'string')
          {
              const textureFromCache = TextureCache[texture];
  
              if (textureFromCache)
              {
                  const index = textureFromCache.textureCacheIds.indexOf(texture);
  
                  if (index > -1)
                  {
                      textureFromCache.textureCacheIds.splice(index, 1);
                  }
  
                  delete TextureCache[texture];
  
                  return textureFromCache;
              }
          }
          else if (texture && texture.textureCacheIds)
          {
              for (let i = 0; i < texture.textureCacheIds.length; ++i)
              {
                  // Check that texture matches the one being passed in before deleting it from the cache.
                  if (TextureCache[texture.textureCacheIds[i]] === texture)
                  {
                      delete TextureCache[texture.textureCacheIds[i]];
                  }
              }
  
              texture.textureCacheIds.length = 0;
  
              return texture;
          }
  
          return null;
      }
  
      /**
       * The frame specifies the region of the base texture that this texture uses.
       *
       * @member {PIXI.Rectangle}
       */
      get frame()
      {
          return this._frame;
      }
  
      set frame(frame) // eslint-disable-line require-jsdoc
      {
          this._frame = frame;
  
          this.noFrame = false;
  
          if (frame.x + frame.width > this.baseTexture.width || frame.y + frame.height > this.baseTexture.height)
          {
              throw new Error('Texture Error: frame does not fit inside the base Texture dimensions: '
                  + `X: ${frame.x} + ${frame.width} = ${frame.x + frame.width} > ${this.baseTexture.width} `
                  + `Y: ${frame.y} + ${frame.height} = ${frame.y + frame.height} > ${this.baseTexture.height}`);
          }
  
          // this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded;
          this.valid = frame && frame.width && frame.height && this.baseTexture.hasLoaded;
  
          if (!this.trim && !this.rotate)
          {
              this.orig = frame;
          }
  
          if (this.valid)
          {
              this._updateUvs();
          }
      }
  
      /**
       * Indicates whether the texture is rotated inside the atlas
       * set to 2 to compensate for texture packer rotation
       * set to 6 to compensate for spine packer rotation
       * can be used to rotate or mirror sprites
       * See {@link PIXI.GroupD8} for explanation
       *
       * @member {number}
       */
      get rotate()
      {
          return this._rotate;
      }
  
      set rotate(rotate) // eslint-disable-line require-jsdoc
      {
          this._rotate = rotate;
          if (this.valid)
          {
              this._updateUvs();
          }
      }
  
      /**
       * The width of the Texture in pixels.
       *
       * @member {number}
       */
      get width()
      {
          return this.orig.width;
      }
  
      /**
       * The height of the Texture in pixels.
       *
       * @member {number}
       */
      get height()
      {
          return this.orig.height;
      }
  }
  
  function createWhiteTexture()
  {
      const canvas = document.createElement('canvas');
  
      canvas.width = 10;
      canvas.height = 10;
  
      const context = canvas.getContext('2d');
  
      context.fillStyle = 'white';
      context.fillRect(0, 0, 10, 10);
  
      return new Texture(new BaseTexture(canvas));
  }
  
  function removeAllHandlers(tex)
  {
      tex.destroy = function _emptyDestroy() { /* empty */ };
      tex.on = function _emptyOn() { /* empty */ };
      tex.once = function _emptyOnce() { /* empty */ };
      tex.emit = function _emptyEmit() { /* empty */ };
  }
  
  /**
   * An empty texture, used often to not have to create multiple empty textures.
   * Can not be destroyed.
   *
   * @static
   * @constant
   */
  Texture.EMPTY = new Texture(new BaseTexture());
  removeAllHandlers(Texture.EMPTY);
  removeAllHandlers(Texture.EMPTY.baseTexture);
  
  /**
   * A white texture of 10x10 size, used for graphics and other things
   * Can not be destroyed.
   *
   * @static
   * @constant
   */
  Texture.WHITE = createWhiteTexture();
  removeAllHandlers(Texture.WHITE);
  removeAllHandlers(Texture.WHITE.baseTexture);
  