import {
  AttributeType,
  BlendingState,
  Cartesian3,
  ComponentDatatype,
  DepthFunction,
  LightingModel,
  Math as CesiumMath,
  Matrix4,
  ModelExperimentalNode,
  ModelExperimentalPrimitive,
  PrimitiveType,
  ModelRenderResources,
  NodeRenderResources,
  PrimitiveRenderResources,
  VertexAttributeSemantic,
} from "../../../Source/Cesium.js";

describe("Scene/ModelExperimental/PrimitiveRenderResources", function () {
  var mockModel = {};
  var mockNode = {};
  var runtimeNode = new ModelExperimentalNode({
    node: mockNode,
    modelMatrix: Matrix4.IDENTITY,
  });

  function checkShaderDefines(shaderBuilder, expectedDefines) {
    expect(shaderBuilder._fragmentShaderParts.defineLines).toEqual(
      expectedDefines
    );
  }

  var primitive = {
    indices: {
      count: 6,
    },
    primitiveType: PrimitiveType.TRIANGLES,
    attributes: [
      {
        semantic: VertexAttributeSemantic.POSITION,
        buffer: new Float32Array([0, 1, 2, 3, 4, 5]).buffer,
        type: AttributeType.VEC3,
        componentDatatype: ComponentDatatype.FLOAT,
        min: new Cartesian3(-1, -1, -1),
        max: new Cartesian3(1, 1, 1),
      },
    ],
  };

  var primitiveWithoutIndices = {
    primitiveType: PrimitiveType.POINTS,
    attributes: [
      {
        semantic: VertexAttributeSemantic.POSITION,
        buffer: new Float32Array([0, 1, 2, 3, 4, 5, 6, 7, 8]).buffer,
        type: AttributeType.VEC3,
        componentDatatype: ComponentDatatype.FLOAT,
        count: 8,
        min: new Cartesian3(-2, -2, -2),
        max: new Cartesian3(2, 2, 2),
      },
    ],
  };

  var runtimePrimitive = new ModelExperimentalPrimitive({
    primitive: primitive,
    model: mockModel,
  });

  var runtimePrimitiveWithoutIndices = new ModelExperimentalPrimitive({
    primitive: primitiveWithoutIndices,
    model: mockModel,
  });

  var expectedDepthTest = {
    depthTest: {
      enabled: true,
      func: DepthFunction.LESS_OR_EQUAL,
    },
    blending: BlendingState.DISABLED,
  };

  it("throws for undefined nodeRenderResources", function () {
    expect(function () {
      return new PrimitiveRenderResources(undefined, runtimePrimitive);
    }).toThrowDeveloperError();
  });

  it("throws for undefined runtimePrimitive", function () {
    expect(function () {
      var modelResources = new ModelRenderResources(mockModel);
      var nodeResources = new NodeRenderResources(modelResources, runtimeNode);
      return new PrimitiveRenderResources(nodeResources, undefined);
    }).toThrowDeveloperError();
  });

  it("constructs", function () {
    var modelResources = new ModelRenderResources(mockModel);
    var nodeResources = new NodeRenderResources(modelResources, runtimeNode);
    var primitiveResources = new PrimitiveRenderResources(
      nodeResources,
      runtimePrimitive
    );

    expect(primitiveResources.runtimePrimitive).toBe(runtimePrimitive);
    expect(primitiveResources.pickId).toBeUndefined();
    expect(primitiveResources.count).toBe(6);
    expect(primitiveResources.indices).toBe(primitive.indices);
    expect(primitiveResources.primitiveType).toBe(PrimitiveType.TRIANGLES);
    // The points are in a cube from -1, -1, -1 to 1, 1, 1. The center is
    // (0, 0, 0). The full diagonal is 2 * sqrt(3), so half is sqrt(3)
    expect(primitiveResources.boundingSphere.center).toEqualEpsilon(
      Cartesian3.ZERO,
      CesiumMath.EPSILON9
    );
    expect(primitiveResources.boundingSphere.radius).toEqualEpsilon(
      Math.sqrt(3),
      CesiumMath.EPSILON9
    );
    expect(primitiveResources.uniformMap).toEqual({});
    expect(primitiveResources.lightingOptions.lightingModel).toEqual(
      LightingModel.UNLIT
    );
    expect(primitiveResources.renderStateOptions).toEqual(expectedDepthTest);
  });

  it("constructs from primitive without indices", function () {
    var modelResources = new ModelRenderResources(mockModel);
    var nodeResources = new NodeRenderResources(modelResources, runtimeNode);
    var primitiveResources = new PrimitiveRenderResources(
      nodeResources,
      runtimePrimitiveWithoutIndices
    );

    expect(primitiveResources.count).toBe(8);
    expect(primitiveResources.indices).not.toBeDefined();
    expect(primitiveResources.primitiveType).toBe(PrimitiveType.POINTS);

    // The points are in a cube from -2, -2, -2 to 2, 2, 2. The center is
    // (0, 0, 0). The full diagonal is 4 * sqrt(3), so half is 2 * sqrt(3)
    expect(primitiveResources.boundingSphere.center).toEqualEpsilon(
      Cartesian3.ZERO,
      CesiumMath.EPSILON9
    );
    expect(primitiveResources.boundingSphere.radius).toEqualEpsilon(
      2.0 * Math.sqrt(3),
      CesiumMath.EPSILON9
    );
    expect(primitiveResources.uniformMap).toEqual({});
    expect(primitiveResources.lightingOptions.lightingModel).toEqual(
      LightingModel.UNLIT
    );
    expect(primitiveResources.renderStateOptions).toEqual(expectedDepthTest);
  });

  it("inherits from model render resources", function () {
    var modelResources = new ModelRenderResources(mockModel);
    modelResources.shaderBuilder.addDefine("MODEL");
    var nodeResources = new NodeRenderResources(modelResources, runtimeNode);
    nodeResources.shaderBuilder.addDefine("NODE");
    var primitiveResources = new PrimitiveRenderResources(
      nodeResources,
      runtimePrimitive
    );
    primitiveResources.shaderBuilder.addDefine("PRIMITIVE");

    expect(primitiveResources.model).toBe(mockModel);

    // The primitive's shader builder should be a clone of the node's
    expect(primitiveResources.shaderBuilder).not.toBe(
      modelResources.shaderBuilder
    );
    expect(primitiveResources.shaderBuilder).not.toBe(
      modelResources.shaderBuilder
    );

    // The defines should cascade through the three levels
    checkShaderDefines(modelResources.shaderBuilder, ["MODEL"]);
    checkShaderDefines(nodeResources.shaderBuilder, ["MODEL", "NODE"]);
    checkShaderDefines(primitiveResources.shaderBuilder, [
      "MODEL",
      "NODE",
      "PRIMITIVE",
    ]);
  });

  it("inherits from node render resources", function () {
    var modelResources = new ModelRenderResources(mockModel);
    modelResources.shaderBuilder.addDefine("MODEL");
    var nodeResources = new NodeRenderResources(modelResources, runtimeNode);
    nodeResources.shaderBuilder.addDefine("NODE");
    var primitiveResources = new PrimitiveRenderResources(
      nodeResources,
      runtimePrimitive
    );
    expect(primitiveResources.runtimeNode).toBe(runtimeNode);
    expect(primitiveResources.modelMatrix).toEqual(runtimeNode.modelMatrix);
    expect(primitiveResources.attributes).toEqual([]);
  });
});
