import {
  GltfLoaderUtil,
  Matrix3,
  PixelDatatype,
  PixelFormat,
  SupportedImageFormats,
  Texture,
  TextureWrap,
  TextureMagnificationFilter,
  TextureMinificationFilter,
} from "../../Source/Cesium.js";
import createContext from "../createContext.js";

describe("Scene/GltfLoaderUtil", function () {
  var gltfWithTextures = {
    images: [
      {
        uri: "image.png",
      },
      {
        mimeType: "image/jpeg",
        bufferView: 0,
      },
      {
        uri: "image.webp",
      },
      {
        uri: "image.ktx2",
      },
    ],
    textures: [
      {
        source: 0,
      },
      {
        source: 0,
        extensions: {
          EXT_texture_webp: {
            source: 2,
          },
        },
      },
      {
        source: 0,
        extensions: {
          KHR_texture_basisu: {
            source: 3,
          },
        },
      },
    ],
  };

  it("getImageIdFromTexture throws if gltf is undefined", function () {
    expect(function () {
      GltfLoaderUtil.getImageIdFromTexture({
        gltf: undefined,
        textureId: 0,
        supportedImageFormats: new SupportedImageFormats(),
      });
    }).toThrowDeveloperError();
  });

  it("getImageIdFromTexture throws if textureId is undefined", function () {
    expect(function () {
      GltfLoaderUtil.getImageIdFromTexture({
        gltf: gltfWithTextures,
        textureId: undefined,
        supportedImageFormats: new SupportedImageFormats(),
      });
    }).toThrowDeveloperError();
  });

  it("getImageIdFromTexture throws if supportedImageFormats is undefined", function () {
    expect(function () {
      GltfLoaderUtil.getImageIdFromTexture({
        gltf: gltfWithTextures,
        textureId: 0,
        supportedImageFormats: undefined,
      });
    }).toThrowDeveloperError();
  });

  it("getImageIdFromTexture gets image id", function () {
    var imageId = GltfLoaderUtil.getImageIdFromTexture({
      gltf: gltfWithTextures,
      textureId: 0,
      supportedImageFormats: new SupportedImageFormats(),
    });
    expect(imageId).toBe(0);
  });

  it("getImageIdFromTexture gets WebP image when EXT_texture_webp extension is supported", function () {
    var imageId = GltfLoaderUtil.getImageIdFromTexture({
      gltf: gltfWithTextures,
      textureId: 1,
      supportedImageFormats: new SupportedImageFormats({
        webp: true,
      }),
    });
    expect(imageId).toBe(2);
  });

  it("getImageIdFromTexture gets default image when EXT_texture_webp extension is not supported", function () {
    var imageId = GltfLoaderUtil.getImageIdFromTexture({
      gltf: gltfWithTextures,
      textureId: 1,
      supportedImageFormats: new SupportedImageFormats({
        webp: false,
      }),
    });
    expect(imageId).toBe(0);
  });

  it("getImageIdFromTexture gets KTX2 image when KHR_texture_basisu extension is supported", function () {
    var imageId = GltfLoaderUtil.getImageIdFromTexture({
      gltf: gltfWithTextures,
      textureId: 2,
      supportedImageFormats: new SupportedImageFormats({
        basis: true,
      }),
    });
    expect(imageId).toBe(3);
  });

  it("getImageIdFromTexture gets default image when KHR_texture_basisu extension is not supported", function () {
    var imageId = GltfLoaderUtil.getImageIdFromTexture({
      gltf: gltfWithTextures,
      textureId: 2,
      supportedImageFormats: new SupportedImageFormats({
        basis: false,
      }),
    });
    expect(imageId).toBe(0);
  });

  it("createSampler throws if gltf is undefined", function () {
    expect(function () {
      GltfLoaderUtil.getImageIdFromTexture({
        gltf: undefined,
        textureInfo: {
          index: 0,
        },
      });
    }).toThrowDeveloperError();
  });

  it("createSampler throws if textureInfo is undefined", function () {
    expect(function () {
      GltfLoaderUtil.getImageIdFromTexture({
        gltf: gltfWithTextures,
        textureInfo: undefined,
      });
    }).toThrowDeveloperError();
  });

  it("createSampler gets default sampler when texture does not have a sampler", function () {
    var sampler = GltfLoaderUtil.createSampler({
      gltf: {
        textures: [
          {
            source: 0,
          },
        ],
      },
      textureInfo: {
        index: 0,
      },
    });
    expect(sampler.wrapS).toBe(TextureWrap.REPEAT);
    expect(sampler.wrapT).toBe(TextureWrap.REPEAT);
    expect(sampler.minificationFilter).toBe(TextureMinificationFilter.LINEAR);
    expect(sampler.magnificationFilter).toBe(TextureMagnificationFilter.LINEAR);
  });

  it("createSampler uses NEAREST when compressedTextureNoMipmap is true and the minFilter uses nearest mipmap filtering", function () {
    var sampler = GltfLoaderUtil.createSampler({
      gltf: {
        textures: [
          {
            source: 0,
            sampler: 0,
          },
        ],
        samplers: [
          {
            magFilter: 9729,
            minFilter: 9986,
            wrapS: 10497,
            wrapT: 10497,
          },
        ],
      },
      textureInfo: {
        index: 0,
      },
      compressedTextureNoMipmap: true,
    });
    expect(sampler.wrapS).toBe(TextureWrap.REPEAT);
    expect(sampler.wrapT).toBe(TextureWrap.REPEAT);
    expect(sampler.minificationFilter).toBe(TextureMinificationFilter.NEAREST);
    expect(sampler.magnificationFilter).toBe(TextureMagnificationFilter.LINEAR);
  });

  it("createSampler uses LINEAR when compressedTextureNoMipmap is true and the minFilter uses linear mipmap filtering", function () {
    var sampler = GltfLoaderUtil.createSampler({
      gltf: {
        textures: [
          {
            source: 0,
            sampler: 0,
          },
        ],
        samplers: [
          {
            magFilter: 9729,
            minFilter: 9987,
            wrapS: 10497,
            wrapT: 10497,
          },
        ],
      },
      textureInfo: {
        index: 0,
      },
      compressedTextureNoMipmap: true,
    });
    expect(sampler.wrapS).toBe(TextureWrap.REPEAT);
    expect(sampler.wrapT).toBe(TextureWrap.REPEAT);
    expect(sampler.minificationFilter).toBe(TextureMinificationFilter.LINEAR);
    expect(sampler.magnificationFilter).toBe(TextureMagnificationFilter.LINEAR);
  });

  function createSampler(options) {
    var gltf = {
      textures: [
        {
          source: 0,
          sampler: 0,
        },
      ],
      samplers: [
        {
          minFilter: options.minFilter,
          magFilter: options.magFilter,
          wrapS: options.wrapS,
          wrapT: options.wrapT,
        },
      ],
    };

    var textureInfo = {
      index: 0,
    };

    if (options.useTextureTransform) {
      textureInfo.extensions = {
        KHR_texture_transform: {},
      };
    }

    return GltfLoaderUtil.createSampler({
      gltf: gltf,
      textureInfo: textureInfo,
    });
  }

  it("createSampler fills in undefined sampler properties", function () {
    var sampler = createSampler({
      wrapS: TextureWrap.CLAMP_TO_EDGE,
    });

    expect(sampler.wrapS).toBe(TextureWrap.CLAMP_TO_EDGE);
    expect(sampler.wrapT).toBe(TextureWrap.REPEAT);
    expect(sampler.minificationFilter).toBe(TextureMinificationFilter.LINEAR);
    expect(sampler.magnificationFilter).toBe(TextureMagnificationFilter.LINEAR);
  });

  it("createSampler creates non-mipmap sampler for KHR_texture_transform", function () {
    var sampler = createSampler({
      minFilter: TextureMinificationFilter.NEAREST_MIPMAP_NEAREST,
      useTextureTransform: true,
    });
    expect(sampler.minificationFilter).toBe(TextureMinificationFilter.NEAREST);

    sampler = createSampler({
      minFilter: TextureMinificationFilter.NEAREST_MIPMAP_LINEAR,
      useTextureTransform: true,
    });
    expect(sampler.minificationFilter).toBe(TextureMinificationFilter.NEAREST);

    sampler = createSampler({
      minFilter: TextureMinificationFilter.LINEAR_MIPMAP_NEAREST,
      useTextureTransform: true,
    });
    expect(sampler.minificationFilter).toBe(TextureMinificationFilter.LINEAR);

    sampler = createSampler({
      minFilter: TextureMinificationFilter.LINEAR_MIPMAP_LINEAR,
      useTextureTransform: true,
    });
    expect(sampler.minificationFilter).toBe(TextureMinificationFilter.LINEAR);
  });

  it("createModelTextureReader creates texture with default values", function () {
    var textureInfo = {
      index: 0,
    };

    var modelTexture = GltfLoaderUtil.createModelTextureReader({
      textureInfo: textureInfo,
    });

    expect(modelTexture.texture).toBeUndefined();
    expect(modelTexture.texCoord).toBe(0);
    expect(modelTexture.transform).toBeUndefined();
    expect(modelTexture.channels).toBeUndefined();
  });

  it("createModelTextureReader creates texture with KHR_texture_transform extension", function () {
    var textureInfo = {
      index: 0,
      texCoord: 0,
      extensions: {
        KHR_texture_transform: {
          offset: [0.5, 0.5],
          scale: [0.1, 0.2],
          texCoord: 1,
        },
      },
    };

    // prettier-ignore
    var expectedTransform = new Matrix3(
      0.1, 0.0, 0.5,
      0.0, 0.2, 0.5,
      0.0, 0.0, 1.0
    );

    var modelTexture = GltfLoaderUtil.createModelTextureReader({
      textureInfo: textureInfo,
    });

    expect(modelTexture.texCoord).toBe(1);
    expect(modelTexture.transform).toEqual(expectedTransform);
  });

  it("createModelTextureReader handles KHR_texture_transform rotation correctly", function () {
    var angle = Math.PI / 2.0;
    var textureInfo = {
      index: 0,
      texCoord: 0,
      extensions: {
        KHR_texture_transform: {
          rotation: angle,
          texCoord: 1,
        },
      },
    };

    // glTF requires texture coordinates to start in the top left corner.
    // This reverses the orientation of the uv coordinate space, so the angle
    // must be reversed.
    // prettier-ignore
    var expectedTransform = new Matrix3(
      Math.cos(-angle), -Math.sin(-angle), 0.0,
      Math.sin(-angle), Math.cos(-angle), 0.0,
      0.0, 0.0, 1.0
    );

    var modelTexture = GltfLoaderUtil.createModelTextureReader({
      textureInfo: textureInfo,
    });

    expect(modelTexture.texCoord).toBe(1);
    expect(modelTexture.transform).toEqual(expectedTransform);
  });

  it("createModelTextureReader creates texture", function () {
    var context = createContext();

    var texture = new Texture({
      context: context,
      pixelFormat: PixelFormat.RGBA,
      pixelDatatype: PixelDatatype.UNSIGNED_BYTE,
      source: {
        arrayBufferView: new Uint8Array([1, 2, 3, 4]),
        width: 1,
        height: 1,
      },
    });

    var textureInfo = {
      index: 0,
      texCoord: 1,
    };

    var modelTexture = GltfLoaderUtil.createModelTextureReader({
      textureInfo: textureInfo,
      channels: "r",
      texture: texture,
    });

    expect(modelTexture.texture).toBe(texture);
    expect(modelTexture.texCoord).toBe(1);
    expect(modelTexture.transform).toBeUndefined();
    expect(modelTexture.channels).toBe("r");

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

  it("createModelTextureReader throws if textureInfo is undefined", function () {
    expect(function () {
      GltfLoaderUtil.createModelTextureReader();
    }).toThrowDeveloperError();
  });
});
