import { Cartesian2 } from "../../Source/Cesium.js";
import { Color } from "../../Source/Cesium.js";
import { loadKTX2 } from "../../Source/Cesium.js";
import { KTX2Transcoder } from "../../Source/Cesium.js";
import { PixelFormat } from "../../Source/Cesium.js";
import { Resource } from "../../Source/Cesium.js";
import { ClearCommand } from "../../Source/Cesium.js";
import { ContextLimits } from "../../Source/Cesium.js";
import { PixelDatatype } from "../../Source/Cesium.js";
import { Sampler } from "../../Source/Cesium.js";
import { Texture } from "../../Source/Cesium.js";
import { TextureMagnificationFilter } from "../../Source/Cesium.js";
import { TextureMinificationFilter } from "../../Source/Cesium.js";
import { TextureWrap } from "../../Source/Cesium.js";
import createContext from "../createContext.js";
import { when } from "../../Source/Cesium.js";

describe(
  "Renderer/Texture",
  function () {
    var context;
    var greenImage;
    var blueImage;
    var blueAlphaImage;
    var blueOverRedImage;
    var blueOverRedFlippedImage;
    var red16x16Image;

    var greenKTX2Image;
    var greenBasisKTX2Image;

    var fs =
      "uniform sampler2D u_texture;" +
      "void main() { gl_FragColor = texture2D(u_texture, vec2(0.0)); }";
    var fsLuminanceAlpha =
      "uniform sampler2D u_texture;" +
      "void main() { gl_FragColor = vec4(texture2D(u_texture, vec2(0.0)).ra, 0.0, 1.0); }";
    var texture;
    var uniformMap = {
      u_texture: function () {
        return texture;
      },
    };

    beforeAll(function () {
      context = createContext();
      var promises = [];
      promises.push(
        Resource.fetchImage("./Data/Images/Green.png").then(function (image) {
          greenImage = image;
        })
      );
      promises.push(
        Resource.fetchImage("./Data/Images/Blue.png").then(function (image) {
          blueImage = image;
        })
      );
      promises.push(
        Resource.fetchImage("./Data/Images/BlueAlpha.png").then(function (
          image
        ) {
          blueAlphaImage = image;
        })
      );
      promises.push(
        Resource.fetchImage("./Data/Images/BlueOverRed.png").then(function (
          image
        ) {
          blueOverRedImage = image;
        })
      );
      // Load this image as an ImageBitmap
      promises.push(
        Resource.fetchImage({
          url: "./Data/Images/BlueOverRed.png",
          preferImageBitmap: true,
        }).then(function (image) {
          blueOverRedFlippedImage = image;
        })
      );
      promises.push(
        Resource.fetchImage("./Data/Images/Red16x16.png").then(function (
          image
        ) {
          red16x16Image = image;
        })
      );

      var resource = Resource.createIfNeeded("./Data/Images/Green4x4.ktx2");
      var loadPromise = resource.fetchArrayBuffer();
      promises.push(
        loadPromise.then(function (buffer) {
          var promise = KTX2Transcoder.transcode(buffer, {});
          return promise.then(function (result) {
            greenKTX2Image = result;
          });
        })
      );

      if (context.supportsBasis) {
        promises.push(
          loadKTX2("./Data/Images/Green4x4_ETC1S.ktx2").then(function (image) {
            greenBasisKTX2Image = image;
          })
        );
      }

      return when.all(promises);
    });

    afterAll(function () {
      context.destroyForSpecs();
    });

    afterEach(function () {
      texture = texture && texture.destroy();
    });

    it("has expected default values for pixel format and datatype", function () {
      texture = new Texture({
        context: context,
        source: blueImage,
      });

      expect(texture.id).toBeDefined();
      expect(texture.pixelFormat).toEqual(PixelFormat.RGBA);
      expect(texture.pixelDatatype).toEqual(PixelDatatype.UNSIGNED_BYTE);
    });

    it("can create a texture from the framebuffer", function () {
      var command = new ClearCommand({
        color: Color.RED,
      });
      command.execute(context);

      texture = Texture.fromFramebuffer({
        context: context,
      });

      var expectedWidth = context.canvas.clientWidth;
      var expectedHeight = context.canvas.clientHeight;
      expect(texture.width).toEqual(expectedWidth);
      expect(texture.height).toEqual(expectedHeight);
      expect(texture.sizeInBytes).toEqual(
        expectedWidth *
          expectedHeight *
          PixelFormat.componentsLength(texture.pixelFormat)
      );

      command.color = Color.WHITE;
      command.execute(context);
      expect(context).toReadPixels([255, 255, 255, 255]);

      expect({
        context: context,
        fragmentShader: fs,
        uniformMap: uniformMap,
      }).contextToRender([255, 0, 0, 255]);
    });

    it("can copy from the framebuffer", function () {
      texture = new Texture({
        context: context,
        source: blueImage,
        pixelFormat: PixelFormat.RGB,
      });

      // Render blue
      expect({
        context: context,
        fragmentShader: fs,
        uniformMap: uniformMap,
      }).contextToRender([0, 0, 255, 255]);

      // Clear to red
      var command = new ClearCommand({
        color: Color.RED,
      });
      command.execute(context);
      expect(context).toReadPixels(Color.RED.toBytes());

      texture.copyFromFramebuffer();

      var expectedWidth = context.canvas.clientWidth;
      var expectedHeight = context.canvas.clientHeight;
      expect(texture.width).toEqual(expectedWidth);
      expect(texture.height).toEqual(expectedHeight);
      expect(texture.sizeInBytes).toEqual(
        expectedWidth *
          expectedHeight *
          PixelFormat.componentsLength(texture.pixelFormat)
      );

      // Clear to white
      command.color = Color.WHITE;
      command.execute(context);
      expect(context).toReadPixels(Color.WHITE.toBytes());

      // Render red
      expect({
        context: context,
        fragmentShader: fs,
        uniformMap: uniformMap,
      }).contextToRender([255, 0, 0, 255]);
    });

    it("draws the expected texture color", function () {
      texture = new Texture({
        context: context,
        source: blueImage,
        pixelFormat: PixelFormat.RGBA,
      });

      expect({
        context: context,
        fragmentShader: fs,
        uniformMap: uniformMap,
      }).contextToRender([0, 0, 255, 255]);
    });

    it("cannot flip texture when using ImageBitmap", function () {
      var topColor = new Color(0.0, 0.0, 1.0, 1.0);
      var bottomColor = new Color(1.0, 0.0, 0.0, 1.0);

      return Resource.supportsImageBitmapOptions().then(function (
        supportsImageBitmapOptions
      ) {
        if (supportsImageBitmapOptions) {
          // When imageBitmapOptions is supported, flipY on texture upload is ignored.
          bottomColor = topColor;
        }

        texture = new Texture({
          context: context,
          source: blueOverRedFlippedImage,
          pixelFormat: PixelFormat.RGBA,
          flipY: false,
        });

        expect({
          context: context,
          fragmentShader: fs,
          uniformMap: uniformMap,
        }).contextToRender(topColor.toBytes());

        // Flip the texture.
        texture = new Texture({
          context: context,
          source: blueOverRedFlippedImage,
          pixelFormat: PixelFormat.RGBA,
          flipY: true,
        });

        expect({
          context: context,
          fragmentShader: fs,
          uniformMap: uniformMap,
        }).contextToRender(bottomColor.toBytes());
      });
    });

    it("draws the expected floating-point texture color", function () {
      if (!context.floatingPointTexture) {
        return;
      }

      var color = new Color(0.2, 0.4, 0.6, 1.0);
      var floats = new Float32Array([
        color.red,
        color.green,
        color.blue,
        color.alpha,
      ]);

      texture = new Texture({
        context: context,
        pixelFormat: PixelFormat.RGBA,
        pixelDatatype: PixelDatatype.FLOAT,
        source: {
          width: 1,
          height: 1,
          arrayBufferView: floats,
        },
      });

      expect(texture.sizeInBytes).toEqual(16);

      expect({
        context: context,
        fragmentShader: fs,
        uniformMap: uniformMap,
      }).contextToRender(color.toBytes());
    });

    it("draws the expected floating-point texture color with linear filtering", function () {
      if (!context.floatingPointTexture) {
        return;
      }

      var color0 = new Color(0.2, 0.4, 0.6, 1.0);
      var color1 = new Color(0.1, 0.3, 0.5, 1.0);
      var floats = new Float32Array([
        color0.red,
        color0.green,
        color0.blue,
        color0.alpha,
        color1.red,
        color1.green,
        color1.blue,
        color1.alpha,
      ]);

      texture = new Texture({
        context: context,
        pixelFormat: PixelFormat.RGBA,
        pixelDatatype: PixelDatatype.FLOAT,
        source: {
          width: 2,
          height: 1,
          arrayBufferView: floats,
        },
        sampler: new Sampler({
          wrapS: TextureWrap.CLAMP_TO_EDGE,
          wrapT: TextureWrap.CLAMP_TO_EDGE,
          minificationFilter: TextureMinificationFilter.LINEAR,
          magnificationFilter: TextureMagnificationFilter.LINEAR,
        }),
      });

      expect(texture.sizeInBytes).toEqual(32);

      var fs =
        "uniform sampler2D u_texture;" +
        "void main() { gl_FragColor = texture2D(u_texture, vec2(0.5, 0.0)); }";

      if (!context.textureFloatLinear) {
        expect({
          context: context,
          fragmentShader: fs,
          uniformMap: uniformMap,
          epsilon: 1,
        }).contextToRender(color1.toBytes());
      } else {
        Color.multiplyByScalar(color0, 1.0 - 0.5, color0);
        Color.multiplyByScalar(color1, 0.5, color1);
        Color.add(color0, color1, color1);
        expect({
          context: context,
          fragmentShader: fs,
          uniformMap: uniformMap,
        }).contextToRender(color1.toBytes());
      }
    });

    it("draws the expected half floating-point texture color", function () {
      if (!context.halfFloatingPointTexture) {
        return;
      }

      var color = new Color(0.2, 0.4, 0.6, 1.0);
      var floats = new Uint16Array([12902, 13926, 14541, 15360]);

      texture = new Texture({
        context: context,
        pixelFormat: PixelFormat.RGBA,
        pixelDatatype: PixelDatatype.HALF_FLOAT,
        source: {
          width: 1,
          height: 1,
          arrayBufferView: floats,
        },
        flipY: false,
      });

      expect(texture.sizeInBytes).toEqual(8);

      expect({
        context: context,
        fragmentShader: fs,
        uniformMap: uniformMap,
      }).contextToRender(color.toBytes());
    });

    it("draws the expected half floating-point texture color with linear filtering", function () {
      if (!context.halfFloatingPointTexture) {
        return;
      }

      var color0 = new Color(0.2, 0.4, 0.6, 1.0);
      var color1 = new Color(0.1, 0.3, 0.5, 1.0);
      var floats = new Uint16Array([
        12902,
        13926,
        14541,
        15360,
        11878,
        13517,
        14336,
        15360,
      ]);

      texture = new Texture({
        context: context,
        pixelFormat: PixelFormat.RGBA,
        pixelDatatype: PixelDatatype.HALF_FLOAT,
        source: {
          width: 2,
          height: 1,
          arrayBufferView: floats,
        },
        flipY: false,
      });

      expect(texture.sizeInBytes).toEqual(16);

      var fs =
        "uniform sampler2D u_texture;" +
        "void main() { gl_FragColor = texture2D(u_texture, vec2(0.5, 0.0)); }";

      if (!context.textureHalfFloatLinear) {
        expect({
          context: context,
          fragmentShader: fs,
          uniformMap: uniformMap,
          epsilon: 1,
        }).contextToRender(color1.toBytes());
      } else {
        Color.multiplyByScalar(color0, 1.0 - 0.5, color0);
        Color.multiplyByScalar(color1, 0.5, color1);
        Color.add(color0, color1, color1);
        expect({
          context: context,
          fragmentShader: fs,
          uniformMap: uniformMap,
        }).contextToRender(color1.toBytes());
      }
    });

    it("draws the expected Basis compressed texture color", function () {
      if (!context.supportsBasis) {
        return;
      }

      texture = new Texture({
        context: context,
        pixelFormat: greenBasisKTX2Image.internalFormat,
        source: {
          width: greenBasisKTX2Image.width,
          height: greenBasisKTX2Image.height,
          arrayBufferView: greenBasisKTX2Image.bufferView,
        },
      });

      expect(texture.sizeInBytes).toBe(8);

      expect({
        context: context,
        fragmentShader: fs,
        uniformMap: uniformMap,
      }).contextToRenderAndCall(function (color) {
        return expect(color).toEqualEpsilon([2, 255, 2, 255], 2);
      });
    });

    it("draws the expected KTX2 uncompressed texture color", function () {
      texture = new Texture({
        context: context,
        pixelFormat: greenKTX2Image.internalFormat,
        source: {
          width: greenKTX2Image.width,
          height: greenKTX2Image.height,
          arrayBufferView: greenKTX2Image.bufferView,
        },
      });

      expect(texture.sizeInBytes).toBe(48);

      expect({
        context: context,
        fragmentShader: fs,
        uniformMap: uniformMap,
      }).contextToRenderAndCall(function (color) {
        return expect(color).toEqualEpsilon([0, 255, 24, 255], 2);
      });
    });

    it("renders with premultiplied alpha", function () {
      var cxt = createContext({
        webgl: {
          alpha: true,
        },
      });
      var texture = new Texture({
        context: cxt,
        source: blueAlphaImage,
        pixelFormat: PixelFormat.RGBA,
        preMultiplyAlpha: true,
      });
      var uniformMap = {
        u_texture: function () {
          return texture;
        },
      };

      expect(texture.preMultiplyAlpha).toEqual(true);
      expect({
        context: cxt,
        fragmentShader: fs,
        uniformMap: uniformMap,
        epsilon: 1,
      }).contextToRender([0, 0, 127, 127]);

      texture.destroy();
      cxt.destroyForSpecs();
    });

    it("draws textured blue and red points", function () {
      texture = new Texture({
        context: context,
        source: blueOverRedImage,
        pixelFormat: PixelFormat.RGBA,
      });

      var fragmentShaderSource = "";
      fragmentShaderSource += "uniform sampler2D u_texture;";
      fragmentShaderSource += "uniform mediump vec2 u_txCoords;";
      fragmentShaderSource +=
        "void main() { gl_FragColor = texture2D(u_texture, u_txCoords); }";

      var txCoords;
      var um = {
        u_texture: function () {
          return texture;
        },
        u_txCoords: function () {
          return txCoords;
        },
      };

      // Blue on top
      txCoords = new Cartesian2(0.5, 0.75);
      expect({
        context: context,
        fragmentShader: fragmentShaderSource,
        uniformMap: um,
      }).contextToRender([0, 0, 255, 255]);

      // Red on bottom
      txCoords = new Cartesian2(0.5, 0.25);
      expect({
        context: context,
        fragmentShader: fragmentShaderSource,
        uniformMap: um,
      }).contextToRender([255, 0, 0, 255]);
    });

    it("draws the expected luminance texture color", function () {
      var color = new Color(0.6, 0.6, 0.6, 1.0);
      var arrayBufferView = new Uint8Array([153]);

      texture = new Texture({
        context: context,
        pixelFormat: PixelFormat.LUMINANCE,
        pixelDatatype: PixelDatatype.UNSIGNED_BYTE,
        source: {
          width: 1,
          height: 1,
          arrayBufferView: arrayBufferView,
        },
        flipY: false,
      });

      expect({
        context: context,
        fragmentShader: fs,
        uniformMap: uniformMap,
      }).contextToRender(color.toBytes());
    });

    it("draws the expected luminance alpha texture color", function () {
      var color = new Color(0.6, 0.8, 0.0, 1.0);
      var arrayBufferView = new Uint8Array([153, 204]);

      texture = new Texture({
        context: context,
        pixelFormat: PixelFormat.LUMINANCE_ALPHA,
        pixelDatatype: PixelDatatype.UNSIGNED_BYTE,
        source: {
          width: 1,
          height: 1,
          arrayBufferView: arrayBufferView,
        },
        flipY: false,
      });

      expect({
        context: context,
        fragmentShader: fsLuminanceAlpha,
        uniformMap: uniformMap,
      }).contextToRender(color.toBytes());
    });

    it("can be created from a typed array", function () {
      var bytes = new Uint8Array([0, 255, 0, 255]);

      texture = new Texture({
        context: context,
        pixelFormat: PixelFormat.RGBA,
        pixelDatatype: PixelDatatype.UNSIGNED_BYTE,
        source: {
          width: 1,
          height: 1,
          arrayBufferView: bytes,
        },
      });

      expect(texture.width).toEqual(1);
      expect(texture.height).toEqual(1);
      expect(texture.sizeInBytes).toEqual(4);

      expect({
        context: context,
        fragmentShader: fs,
        uniformMap: uniformMap,
      }).contextToRender([0, 255, 0, 255]);
    });

    it("can copy from a typed array", function () {
      texture = new Texture({
        context: context,
        pixelFormat: PixelFormat.RGBA,
        pixelDatatype: PixelDatatype.UNSIGNED_BYTE,
        width: 1,
        height: 1,
      });

      var bytes = new Uint8Array(Color.NAVY.toBytes());
      texture.copyFrom({
        source: {
          width: 1,
          height: 1,
          arrayBufferView: bytes,
        },
      });

      expect(texture.width).toEqual(1);
      expect(texture.height).toEqual(1);
      expect(texture.sizeInBytes).toEqual(4);

      expect({
        context: context,
        fragmentShader: fs,
        uniformMap: uniformMap,
      }).contextToRender(Color.NAVY.toBytes());
    });

    it("can copy from a DOM element", function () {
      texture = new Texture({
        context: context,
        pixelFormat: PixelFormat.RGB,
        pixelDatatype: PixelDatatype.UNSIGNED_BYTE,
        width: blueImage.width,
        height: blueImage.height,
      });

      texture.copyFrom({
        source: blueImage,
      });

      expect({
        context: context,
        fragmentShader: fs,
        uniformMap: uniformMap,
        epsilon: 1,
      }).contextToRender([0, 0, 255, 255]);
    });

    it("can replace a subset of a texture", function () {
      texture = new Texture({
        context: context,
        source: blueOverRedImage,
        pixelFormat: PixelFormat.RGBA,
      });

      var fragmentShaderSource = "";
      fragmentShaderSource += "uniform sampler2D u_texture;";
      fragmentShaderSource += "uniform mediump vec2 u_txCoords;";
      fragmentShaderSource +=
        "void main() { gl_FragColor = texture2D(u_texture, u_txCoords); }";

      var txCoords;
      var um = {
        u_texture: function () {
          return texture;
        },
        u_txCoords: function () {
          return txCoords;
        },
      };

      // Blue on top
      txCoords = new Cartesian2(0.5, 0.75);
      expect({
        context: context,
        fragmentShader: fragmentShaderSource,
        uniformMap: um,
      }).contextToRender([0, 0, 255, 255]);

      // Red on bottom
      txCoords = new Cartesian2(0.5, 0.25);
      expect({
        context: context,
        fragmentShader: fragmentShaderSource,
        uniformMap: um,
      }).contextToRender([255, 0, 0, 255]);

      // After copy...
      texture.copyFrom({
        source: greenImage,
        xOffset: 0,
        yOffset: 1,
      });

      // Now green on top
      txCoords = new Cartesian2(0.5, 0.75);
      expect({
        context: context,
        fragmentShader: fragmentShaderSource,
        uniformMap: um,
      }).contextToRender(Color.LIME.toBytes());

      // Still red on bottom
      txCoords = new Cartesian2(0.5, 0.25);
      expect({
        context: context,
        fragmentShader: fragmentShaderSource,
        uniformMap: um,
      }).contextToRender([255, 0, 0, 255]);
    });

    it("can generate mipmaps", function () {
      texture = new Texture({
        context: context,
        source: red16x16Image,
        pixelFormat: PixelFormat.RGBA,
        sampler: new Sampler({
          minificationFilter: TextureMinificationFilter.NEAREST_MIPMAP_LINEAR,
        }),
      });
      texture.generateMipmap();
      expect(texture.sizeInBytes).toEqualEpsilon(
        (16 * 16 + 8 * 8 + 4 * 4 + 2 * 2 + 1) * 4,
        1
      );

      expect({
        context: context,
        fragmentShader: fs,
        uniformMap: uniformMap,
      }).contextToRender([255, 0, 0, 255]);
    });

    it("can set a sampler property", function () {
      texture = new Texture({
        context: context,
        source: blueImage,
        pixelFormat: PixelFormat.RGBA,
      });

      var sampler = new Sampler({
        wrapS: TextureWrap.REPEAT,
        wrapT: TextureWrap.MIRRORED_REPEAT,
        minificationFilter: TextureMinificationFilter.NEAREST,
        magnificationFilter: TextureMagnificationFilter.NEAREST,
        maximumAnisotropy: 2.0,
      });
      texture.sampler = sampler;

      var s = texture.sampler;
      expect(s.wrapS).toEqual(sampler.wrapS);
      expect(s.wrapT).toEqual(sampler.wrapT);
      expect(s.minificationFilter).toEqual(sampler.minificationFilter);
      expect(s.magnificationFilter).toEqual(sampler.magnificationFilter);
      expect(s.maximumAnisotropy).toEqual(2.0);
    });

    it("can set sampler at construction", function () {
      texture = new Texture({
        context: context,
        source: blueImage,
        pixelFormat: PixelFormat.RGBA,
        sampler: new Sampler({
          wrapS: TextureWrap.REPEAT,
          wrapT: TextureWrap.MIRRORED_REPEAT,
          minificationFilter: TextureMinificationFilter.NEAREST,
          magnificationFilter: TextureMagnificationFilter.NEAREST,
          maximumAnisotropy: 2.0,
        }),
      });

      var s = texture.sampler;
      expect(s.wrapS).toEqual(TextureWrap.REPEAT);
      expect(s.wrapT).toEqual(TextureWrap.MIRRORED_REPEAT);
      expect(s.minificationFilter).toEqual(TextureMinificationFilter.NEAREST);
      expect(s.magnificationFilter).toEqual(TextureMagnificationFilter.NEAREST);
      expect(s.maximumAnisotropy).toEqual(2.0);
    });

    it("can get width and height", function () {
      texture = new Texture({
        context: context,
        source: blueOverRedImage,
        pixelFormat: PixelFormat.RGBA,
      });

      expect(texture.width).toEqual(1);
      expect(texture.height).toEqual(2);
    });

    it("can get whether Y is flipped", function () {
      texture = new Texture({
        context: context,
        source: blueOverRedImage,
        pixelFormat: PixelFormat.RGBA,
        flipY: true,
      });

      expect(texture.flipY).toEqual(true);
    });

    it("can get the dimensions of a texture", function () {
      texture = new Texture({
        context: context,
        width: 64,
        height: 16,
      });

      expect(texture.dimensions).toEqual(new Cartesian2(64, 16));
    });

    function expectTextureByteSize(
      width,
      height,
      pixelFormat,
      pixelDatatype,
      expectedSize
    ) {
      texture = new Texture({
        context: context,
        width: width,
        height: height,
        pixelFormat: pixelFormat,
        pixelDatatype: pixelDatatype,
      });
      expect(texture.sizeInBytes).toBe(expectedSize);
      texture = texture && texture.destroy();
    }

    it("can get the size in bytes of a texture", function () {
      // Depth textures
      if (context.depthTexture) {
        expectTextureByteSize(
          16,
          16,
          PixelFormat.DEPTH_COMPONENT,
          PixelDatatype.UNSIGNED_SHORT,
          256 * 2
        );
        expectTextureByteSize(
          16,
          16,
          PixelFormat.DEPTH_COMPONENT,
          PixelDatatype.UNSIGNED_INT,
          256 * 4
        );
        expectTextureByteSize(
          16,
          16,
          PixelFormat.DEPTH_STENCIL,
          PixelDatatype.UNSIGNED_INT_24_8,
          256 * 4
        );
      }

      // Uncompressed formats
      expectTextureByteSize(
        16,
        16,
        PixelFormat.ALPHA,
        PixelDatatype.UNSIGNED_BYTE,
        256
      );
      expectTextureByteSize(
        16,
        16,
        PixelFormat.RGB,
        PixelDatatype.UNSIGNED_BYTE,
        256 * 3
      );
      expectTextureByteSize(
        16,
        16,
        PixelFormat.RGBA,
        PixelDatatype.UNSIGNED_BYTE,
        256 * 4
      );
      expectTextureByteSize(
        16,
        16,
        PixelFormat.LUMINANCE,
        PixelDatatype.UNSIGNED_BYTE,
        256
      );
      expectTextureByteSize(
        16,
        16,
        PixelFormat.LUMINANCE_ALPHA,
        PixelDatatype.UNSIGNED_BYTE,
        256 * 2
      );
    });

    it("can be destroyed", function () {
      var t = new Texture({
        context: context,
        source: blueImage,
        pixelFormat: PixelFormat.RGBA,
      });

      expect(t.isDestroyed()).toEqual(false);
      t.destroy();
      expect(t.isDestroyed()).toEqual(true);
    });

    it("throws when creating a texture without a options", function () {
      expect(function () {
        texture = new Texture();
      }).toThrowDeveloperError();
    });

    it("throws when creating a texture without a source", function () {
      expect(function () {
        texture = new Texture({
          context: context,
        });
      }).toThrowDeveloperError();
    });

    it("throws when creating a texture with width and no height", function () {
      expect(function () {
        texture = new Texture({
          context: context,
          width: 16,
        });
      }).toThrowDeveloperError();
    });

    it("throws when creating a texture with height and no width", function () {
      expect(function () {
        texture = new Texture({
          context: context,
          height: 16,
        });
      }).toThrowDeveloperError();
    });

    it("throws when creating a texture with zero width", function () {
      expect(function () {
        texture = new Texture({
          context: context,
          width: 0,
          height: 16,
        });
      }).toThrowDeveloperError();
    });

    it("throws when creating a texture with width larger than the maximum texture size", function () {
      expect(function () {
        texture = new Texture({
          context: context,
          width: ContextLimits.maximumTextureSize + 1,
          height: 16,
        });
      }).toThrowDeveloperError();
    });

    it("throws when creating a texture with zero height", function () {
      expect(function () {
        texture = new Texture({
          context: context,
          width: 16,
          height: 0,
        });
      }).toThrowDeveloperError();
    });

    it("throws when creating a texture with height larger than the maximum texture size", function () {
      expect(function () {
        texture = new Texture({
          context: context,
          width: 16,
          height: ContextLimits.maximumTextureSize + 1,
        });
      }).toThrowDeveloperError();
    });

    it("throws when creating a texture with an invalid pixel format", function () {
      expect(function () {
        texture = new Texture({
          context: context,
          source: blueImage,
          pixelFormat: "invalid PixelFormat",
        });
      }).toThrowDeveloperError();
    });

    it("throws when creating a texture with an invalid pixel datatype", function () {
      expect(function () {
        texture = new Texture({
          context: context,
          source: blueImage,
          pixelFormat: PixelFormat.RGBA,
          pixelDatatype: "invalid pixelDatatype",
        });
      }).toThrowDeveloperError();
    });

    it("throws when creating if pixelFormat is DEPTH_COMPONENT and pixelDatatype is not UNSIGNED_SHORT or UNSIGNED_INT", function () {
      expect(function () {
        texture = new Texture({
          context: context,
          pixelFormat: PixelFormat.DEPTH_COMPONENT,
          pixelDatatype: PixelDatatype.UNSIGNED_BYTE,
        });
      }).toThrowDeveloperError();
    });

    it("throws when creating if pixelFormat is DEPTH_STENCIL and pixelDatatype is not UNSIGNED_INT_24_8", function () {
      expect(function () {
        texture = new Texture({
          context: context,
          pixelFormat: PixelFormat.DEPTH_STENCIL,
          pixelDatatype: PixelDatatype.UNSIGNED_BYTE,
        });
      }).toThrowDeveloperError();
    });

    it("throws when creating if pixelFormat is DEPTH_COMPONENT or DEPTH_STENCIL, and source is provided", function () {
      expect(function () {
        texture = new Texture({
          context: context,
          source: blueImage,
          pixelFormat: PixelFormat.DEPTH_COMPONENT,
          pixelDatatype: PixelDatatype.UNSIGNED_SHORT,
        });
      }).toThrowDeveloperError();
    });

    it("throws when creating if pixelFormat is DEPTH_COMPONENT or DEPTH_STENCIL, and WEBGL_depth_texture is not supported", function () {
      if (!context.depthTexture) {
        expect(function () {
          texture = new Texture({
            context: context,
            width: 1,
            height: 1,
            pixelFormat: PixelFormat.DEPTH_COMPONENT,
            pixelDatatype: PixelDatatype.UNSIGNED_SHORT,
          });
        }).toThrowDeveloperError();
      }
    });

    it("throws when creating if pixelDatatype is FLOAT, and OES_texture_float is not supported", function () {
      if (!context.floatingPointTexture) {
        expect(function () {
          texture = new Texture({
            context: context,
            width: 1,
            height: 1,
            pixelFormat: PixelFormat.RGBA,
            pixelDatatype: PixelDatatype.FLOAT,
          });
        }).toThrowDeveloperError();
      }
    });

    it("throws when creating if pixelDatatype = HALF_FLOAT, and OES_texture_half_float is not supported", function () {
      if (!context.halfFloatingPointTexture) {
        expect(function () {
          texture = new Texture({
            context: context,
            width: 1,
            height: 1,
            pixelFormat: PixelDatatype.RGBA,
            pixelDatatype: PixelDatatype.HALF_FLOAT,
          });
        }).toThrowDeveloperError();
      }
    });

    it("throws when creating compressed texture and the array buffer source is undefined", function () {
      expect(function () {
        texture = new Texture({
          context: context,
          width: 4,
          height: 4,
          pixelFormat: PixelFormat.RGBA_DXT3,
        });
      }).toThrowDeveloperError();
    });

    it("throws when creating from the framebuffer with an invalid pixel format", function () {
      expect(function () {
        texture = Texture.fromFramebuffer({
          context: context,
          pixelFormat: "invalid PixelFormat",
        });
      }).toThrowDeveloperError();
    });

    it("throws when creating from the framebuffer if PixelFormat is DEPTH_COMPONENT or DEPTH_STENCIL", function () {
      expect(function () {
        texture = Texture.fromFramebuffer({
          context: context,
          pixelFormat: PixelFormat.DEPTH_COMPONENT,
        });
      }).toThrowDeveloperError();
    });

    it("throws when creating from the framebuffer with a negative framebufferXOffset", function () {
      expect(function () {
        texture = Texture.fromFramebuffer({
          context: context,
          pixelFormat: PixelFormat.RGB,
          framebufferXOffset: -1,
        });
      }).toThrowDeveloperError();
    });

    it("throws when creating from the framebuffer with a negative framebufferYOffset", function () {
      expect(function () {
        texture = Texture.fromFramebuffer({
          context: context,
          pixelFormat: PixelFormat.RGB,
          framebufferXOffset: 0,
          framebufferYOffset: -1,
        });
      }).toThrowDeveloperError();
    });

    it("throws when creating from the framebuffer with a width greater than the canvas clientWidth", function () {
      expect(function () {
        texture = Texture.fromFramebuffer({
          context: context,
          pixelFormat: PixelFormat.RGB,
          framebufferXOffset: 0,
          framebufferYOffset: 0,
          width: context.canvas.clientWidth + 1,
        });
      }).toThrowDeveloperError();
    });

    it("throws when creating from the framebuffer with a height greater than the canvas clientHeight", function () {
      expect(function () {
        texture = Texture.fromFramebuffer({
          context: context,
          pixelFormat: PixelFormat.RGB,
          framebufferXOffset: 0,
          framebufferYOffset: 0,
          width: 1,
          height: context.canvas.clientHeight + 1,
        });
      }).toThrowDeveloperError();
    });

    it("throws when copying to a texture from the framebuffer with a DEPTH_COMPONENT or DEPTH_STENCIL pixel format", function () {
      if (context.depthTexture) {
        texture = new Texture({
          context: context,
          width: 1,
          height: 1,
          pixelFormat: PixelFormat.DEPTH_COMPONENT,
          pixelDatatype: PixelDatatype.UNSIGNED_SHORT,
        });

        expect(function () {
          texture.copyFromFramebuffer();
        }).toThrowDeveloperError();
      }
    });

    it("throws when copying to a texture from the framebuffer with a compressed pixel format", function () {
      if (context.supportsBasis) {
        texture = new Texture({
          context: context,
          width: greenBasisKTX2Image.width,
          height: greenBasisKTX2Image.height,
          pixelFormat: greenBasisKTX2Image.internalFormat,
          source: {
            arrayBufferView: greenBasisKTX2Image.bufferView,
          },
        });

        expect(function () {
          texture.copyFromFramebuffer();
        }).toThrowDeveloperError();
      }
    });

    it("throws when copying to a texture from the framebuffer with a FLOAT pixel data type", function () {
      if (context.floatingPointTexture) {
        texture = new Texture({
          context: context,
          width: 1,
          height: 1,
          pixelFormat: PixelFormat.RGBA,
          pixelDatatype: PixelDatatype.FLOAT,
        });

        expect(function () {
          texture.copyFromFramebuffer();
        }).toThrowDeveloperError();
      }
    });

    it("throws when copying to a texture from the framebuffer with a HALF_FLOAT pixel data type", function () {
      if (context.halfFloatingPointTexture) {
        texture = new Texture({
          context: context,
          width: 1,
          height: 1,
          pixelFormat: PixelFormat.RGBA,
          pixelDatatype: PixelDatatype.HALF_FLOAT,
        });

        expect(function () {
          texture.copyFromFramebuffer();
        }).toThrowDeveloperError();
      }
    });

    it("throws when copying from the framebuffer with a negative xOffset", function () {
      texture = new Texture({
        context: context,
        source: blueImage,
      });

      expect(function () {
        texture.copyFromFramebuffer(-1);
      }).toThrowDeveloperError();
    });

    it("throws when copying from the framebuffer with a negative yOffset", function () {
      texture = new Texture({
        context: context,
        source: blueImage,
      });

      expect(function () {
        texture.copyFromFramebuffer(0, -1);
      }).toThrowDeveloperError();
    });

    it("throws when copying from the framebuffer with a negative framebufferXOffset", function () {
      texture = new Texture({
        context: context,
        source: blueImage,
      });

      expect(function () {
        texture.copyFromFramebuffer(0, 0, -1);
      }).toThrowDeveloperError();
    });

    it("throws when copying from the framebuffer with a negative framebufferYOffset", function () {
      texture = new Texture({
        context: context,
        source: blueImage,
      });

      expect(function () {
        texture.copyFromFramebuffer(0, 0, 0, -1);
      }).toThrowDeveloperError();
    });

    it("throws when copying from the framebuffer with a larger width", function () {
      texture = new Texture({
        context: context,
        source: blueImage,
      });

      expect(function () {
        texture.copyFromFramebuffer(0, 0, 0, 0, texture.width + 1);
      }).toThrowDeveloperError();
    });

    it("throws when copying from the framebuffer with a larger height", function () {
      texture = new Texture({
        context: context,
        source: blueImage,
      });

      expect(function () {
        texture.copyFromFramebuffer(0, 0, 0, 0, 0, texture.height + 1);
      }).toThrowDeveloperError();
    });

    it("throws when copying to a texture with a DEPTH_COMPONENT or DEPTH_STENCIL pixel format", function () {
      if (context.depthTexture) {
        texture = new Texture({
          context: context,
          width: 1,
          height: 1,
          pixelFormat: PixelFormat.DEPTH_COMPONENT,
          pixelDatatype: PixelDatatype.UNSIGNED_SHORT,
        });

        expect(function () {
          texture.copyFrom({
            source: {
              arrayBufferView: new Uint16Array([0]),
              width: 1,
              height: 1,
            },
          });
        }).toThrowDeveloperError();
      }
    });

    it("throws when copyFrom is not given any options", function () {
      texture = new Texture({
        context: context,
        source: blueImage,
      });

      expect(function () {
        texture.copyFrom();
      }).toThrowDeveloperError();
    });

    it("throws when copyFrom is not given a source", function () {
      texture = new Texture({
        context: context,
        source: blueImage,
      });

      expect(function () {
        texture.copyFrom({
          xOffset: 0,
          yOffset: 2,
        });
      }).toThrowDeveloperError();
    });

    it("throws when copyFrom is given a negative xOffset", function () {
      texture = new Texture({
        context: context,
        source: blueImage,
      });

      expect(function () {
        texture.copyFrom({
          source: blueImage,
          xOffset: -1,
        });
      }).toThrowDeveloperError();
    });

    it("throws when copyFrom is given a negative yOffset", function () {
      texture = new Texture({
        context: context,
        source: blueImage,
      });

      expect(function () {
        texture.copyFrom({
          source: blueImage,
          xOffset: 0,
          yOffset: -1,
        });
      }).toThrowDeveloperError();
    });

    it("throws when copyFrom is given a source with larger width", function () {
      texture = new Texture({
        context: context,
        source: blueImage,
      });
      var image = new Image();
      image.width = blueImage.width + 1;

      expect(function () {
        texture.copyFrom({
          source: image,
        });
      }).toThrowDeveloperError();
    });

    it("throws when copyFrom is given a source with larger height", function () {
      texture = new Texture({
        context: context,
        source: blueImage,
      });
      var image = new Image();
      image.height = blueImage.height + 1;

      expect(function () {
        texture.copyFrom({
          source: image,
        });
      }).toThrowDeveloperError();
    });

    it("throws when copyFrom is given a source with a compressed pixel format", function () {
      if (context.supportsBasis) {
        texture = new Texture({
          context: context,
          width: greenBasisKTX2Image.width,
          height: greenBasisKTX2Image.height,
          pixelFormat: greenBasisKTX2Image.internalFormat,
          source: {
            arrayBufferView: greenBasisKTX2Image.bufferView,
          },
        });

        var image = new Image();
        expect(function () {
          texture.copyFrom({
            source: image,
          });
        }).toThrowDeveloperError();
      }
    });

    it("throws when generating mipmaps with a DEPTH_COMPONENT or DEPTH_STENCIL pixel format", function () {
      if (context.depthTexture) {
        texture = new Texture({
          context: context,
          width: 1,
          height: 1,
          pixelFormat: PixelFormat.DEPTH_COMPONENT,
          pixelDatatype: PixelDatatype.UNSIGNED_SHORT,
        });

        expect(function () {
          texture.generateMipmap();
        }).toThrowDeveloperError();
      }
    });

    it("throws when generating mipmaps with a compressed pixel format", function () {
      if (context.supportsBasis) {
        texture = new Texture({
          context: context,
          width: greenBasisKTX2Image.width,
          height: greenBasisKTX2Image.height,
          pixelFormat: greenBasisKTX2Image.internalFormat,
          source: {
            arrayBufferView: greenBasisKTX2Image.bufferView,
          },
        });

        expect(function () {
          texture.generateMipmap();
        }).toThrowDeveloperError();
      }
    });

    it("throws when generating mipmaps with a non-power of two width", function () {
      texture = new Texture({
        context: context,
        width: 3,
        height: 2,
      });

      expect(function () {
        texture.generateMipmap();
      }).toThrowDeveloperError();
    });

    it("throws when generating mipmaps with a non-power of two height", function () {
      texture = new Texture({
        context: context,
        width: 2,
        height: 3,
      });

      expect(function () {
        texture.generateMipmap();
      }).toThrowDeveloperError();
    });

    it("throws when generating mipmaps with an invalid hint", function () {
      texture = new Texture({
        context: context,
        source: blueImage,
      });

      expect(function () {
        texture.generateMipmap("invalid hint");
      }).toThrowDeveloperError();
    });

    it("throws when destroy is called after destroying", function () {
      var t = new Texture({
        context: context,
        source: blueImage,
        pixelFormat: PixelFormat.RGBA,
      });

      t.destroy();

      expect(function () {
        t.destroy();
      }).toThrowDeveloperError();
    });
  },
  "WebGL"
);
