import { arrayFill } from "../../Source/Cesium.js";
import { Cartesian3 } from "../../Source/Cesium.js";
import { CornerType } from "../../Source/Cesium.js";
import { CorridorGeometry } from "../../Source/Cesium.js";
import { Ellipsoid } from "../../Source/Cesium.js";
import { GeometryOffsetAttribute } from "../../Source/Cesium.js";
import { Math as CesiumMath } from "../../Source/Cesium.js";
import { Rectangle } from "../../Source/Cesium.js";
import { VertexFormat } from "../../Source/Cesium.js";
import createPackableSpecs from "../createPackableSpecs.js";

describe("Core/CorridorGeometry", function () {
  it("throws without positions", function () {
    expect(function () {
      return new CorridorGeometry({});
    }).toThrowDeveloperError();
  });

  it("throws without width", function () {
    expect(function () {
      return new CorridorGeometry({
        positions: [new Cartesian3()],
      });
    }).toThrowDeveloperError();
  });

  it("createGeometry returns undefined without 2 unique positions", function () {
    var geometry = CorridorGeometry.createGeometry(
      new CorridorGeometry({
        positions: Cartesian3.fromDegreesArray([90.0, -30.0, 90.0, -30.0]),
        width: 10000,
      })
    );
    expect(geometry).toBeUndefined();

    geometry = CorridorGeometry.createGeometry(
      new CorridorGeometry({
        positions: [
          new Cartesian3(
            -1349511.388149118,
            -5063973.22857992,
            3623141.6372688496
          ), //same lon/lat, different height
          new Cartesian3(
            -1349046.4811926484,
            -5062228.688739784,
            3621885.0521561056
          ),
        ],
        width: 10000,
      })
    );
    expect(geometry).toBeUndefined();
  });

  it("computes positions", function () {
    var m = CorridorGeometry.createGeometry(
      new CorridorGeometry({
        vertexFormat: VertexFormat.POSITION_ONLY,
        positions: Cartesian3.fromDegreesArray([90.0, -30.0, 90.0, -35.0]),
        cornerType: CornerType.MITERED,
        width: 30000,
      })
    );

    var numVertices = 12; //6 left + 6 right
    var numTriangles = 10; //5 segments x 2 triangles per segment
    expect(m.attributes.position.values.length).toEqual(numVertices * 3);
    expect(m.indices.length).toEqual(numTriangles * 3);
  });

  it("compute all vertex attributes", function () {
    var m = CorridorGeometry.createGeometry(
      new CorridorGeometry({
        vertexFormat: VertexFormat.ALL,
        positions: Cartesian3.fromDegreesArray([90.0, -30.0, 90.0, -35.0]),
        cornerType: CornerType.MITERED,
        width: 30000,
      })
    );

    var numVertices = 12;
    var numTriangles = 10;
    expect(m.attributes.position.values.length).toEqual(numVertices * 3);
    expect(m.attributes.st.values.length).toEqual(numVertices * 2);
    expect(m.attributes.normal.values.length).toEqual(numVertices * 3);
    expect(m.attributes.tangent.values.length).toEqual(numVertices * 3);
    expect(m.attributes.bitangent.values.length).toEqual(numVertices * 3);
    expect(m.indices.length).toEqual(numTriangles * 3);
  });

  it("computes positions extruded", function () {
    var m = CorridorGeometry.createGeometry(
      new CorridorGeometry({
        vertexFormat: VertexFormat.POSITION_ONLY,
        positions: Cartesian3.fromDegreesArray([90.0, -30.0, 90.0, -35.0]),
        cornerType: CornerType.MITERED,
        width: 30000,
        extrudedHeight: 30000,
      })
    );

    var numVertices = 72; // 6 positions x 4 for a box at each position x 3 to duplicate for normals
    var numTriangles = 44; // 5 segments * 8 triangles per segment + 2 triangles x 2 ends
    expect(m.attributes.position.values.length).toEqual(numVertices * 3);
    expect(m.indices.length).toEqual(numTriangles * 3);
  });

  it("compute all vertex attributes extruded", function () {
    var m = CorridorGeometry.createGeometry(
      new CorridorGeometry({
        vertexFormat: VertexFormat.ALL,
        positions: Cartesian3.fromDegreesArray([90.0, -30.0, 90.0, -35.0]),
        cornerType: CornerType.MITERED,
        width: 30000,
        extrudedHeight: 30000,
      })
    );

    var numVertices = 72;
    var numTriangles = 44;
    expect(m.attributes.position.values.length).toEqual(numVertices * 3);
    expect(m.attributes.st.values.length).toEqual(numVertices * 2);
    expect(m.attributes.normal.values.length).toEqual(numVertices * 3);
    expect(m.attributes.tangent.values.length).toEqual(numVertices * 3);
    expect(m.attributes.bitangent.values.length).toEqual(numVertices * 3);
    expect(m.indices.length).toEqual(numTriangles * 3);
  });

  it("computes offset attribute", function () {
    var m = CorridorGeometry.createGeometry(
      new CorridorGeometry({
        vertexFormat: VertexFormat.POSITION_ONLY,
        positions: Cartesian3.fromDegreesArray([90.0, -30.0, 90.0, -35.0]),
        cornerType: CornerType.MITERED,
        width: 30000,
        offsetAttribute: GeometryOffsetAttribute.TOP,
      })
    );

    var numVertices = 12;
    expect(m.attributes.position.values.length).toEqual(numVertices * 3);

    var offset = m.attributes.applyOffset.values;
    expect(offset.length).toEqual(numVertices);
    var expected = new Array(offset.length);
    expected = arrayFill(expected, 1);
    expect(offset).toEqual(expected);
  });

  it("computes offset attribute extruded for top vertices", function () {
    var m = CorridorGeometry.createGeometry(
      new CorridorGeometry({
        vertexFormat: VertexFormat.POSITION_ONLY,
        positions: Cartesian3.fromDegreesArray([90.0, -30.0, 90.0, -35.0]),
        cornerType: CornerType.MITERED,
        width: 30000,
        extrudedHeight: 30000,
        offsetAttribute: GeometryOffsetAttribute.TOP,
      })
    );

    var numVertices = 72;
    expect(m.attributes.position.values.length).toEqual(numVertices * 3);

    var offset = m.attributes.applyOffset.values;
    expect(offset.length).toEqual(numVertices);
    var expected = new Array(offset.length);
    expected = arrayFill(expected, 0);
    expected = arrayFill(expected, 1, 0, 12);
    expected = arrayFill(expected, 1, 24, 48);
    expect(offset).toEqual(expected);
  });

  it("computes offset attribute extruded for all vertices", function () {
    var m = CorridorGeometry.createGeometry(
      new CorridorGeometry({
        vertexFormat: VertexFormat.POSITION_ONLY,
        positions: Cartesian3.fromDegreesArray([90.0, -30.0, 90.0, -35.0]),
        cornerType: CornerType.MITERED,
        width: 30000,
        extrudedHeight: 30000,
        offsetAttribute: GeometryOffsetAttribute.ALL,
      })
    );

    var numVertices = 72;
    expect(m.attributes.position.values.length).toEqual(numVertices * 3);

    var offset = m.attributes.applyOffset.values;
    expect(offset.length).toEqual(numVertices);
    var expected = new Array(offset.length);
    expected = arrayFill(expected, 1);
    expect(offset).toEqual(expected);
  });

  it("computes right turn", function () {
    var m = CorridorGeometry.createGeometry(
      new CorridorGeometry({
        vertexFormat: VertexFormat.POSITION_ONLY,
        positions: Cartesian3.fromDegreesArray([
          90.0,
          -30.0,
          90.0,
          -31.0,
          91.0,
          -31.0,
        ]),
        cornerType: CornerType.MITERED,
        width: 30000,
      })
    );

    expect(m.attributes.position.values.length).toEqual(8 * 3); // 4 left + 4 right
    expect(m.indices.length).toEqual(6 * 3); // 3 segments * 2 triangles per segment
  });

  it("computes left turn", function () {
    var m = CorridorGeometry.createGeometry(
      new CorridorGeometry({
        vertexFormat: VertexFormat.POSITION_ONLY,
        positions: Cartesian3.fromDegreesArray([
          90.0,
          -30.0,
          90.0,
          -31.0,
          89.0,
          -31.0,
        ]),
        cornerType: CornerType.MITERED,
        width: 30000,
      })
    );

    expect(m.attributes.position.values.length).toEqual(8 * 3);
    expect(m.indices.length).toEqual(6 * 3);
  });

  it("computes with rounded corners", function () {
    var m = CorridorGeometry.createGeometry(
      new CorridorGeometry({
        vertexFormat: VertexFormat.POSITION_AND_ST,
        positions: Cartesian3.fromDegreesArray([
          90.0,
          -30.0,
          90.0,
          -31.0,
          89.0,
          -31.0,
          89.0,
          -32.0,
        ]),
        cornerType: CornerType.ROUNDED,
        width: 30000,
      })
    );

    var endCaps = 72; // 36 points * 2 end caps
    var corners = 37; // 18 for one corner + 19 for the other
    var numVertices = 10 + endCaps + corners;
    var numTriangles = 8 + endCaps + corners;
    expect(m.attributes.position.values.length).toEqual(numVertices * 3);
    expect(m.attributes.st.values.length).toEqual(numVertices * 2);
    expect(m.indices.length).toEqual(numTriangles * 3);
  });

  it("computes with beveled corners", function () {
    var m = CorridorGeometry.createGeometry(
      new CorridorGeometry({
        vertexFormat: VertexFormat.POSITION_ONLY,
        positions: Cartesian3.fromDegreesArray([
          90.0,
          -30.0,
          90.0,
          -31.0,
          89.0,
          -31.0,
          89.0,
          -32.0,
        ]),
        cornerType: CornerType.BEVELED,
        width: 30000,
      })
    );

    expect(m.attributes.position.values.length).toEqual(10 * 3);
    expect(m.indices.length).toEqual(8 * 3);
  });

  it("computes sharp turns", function () {
    var m = CorridorGeometry.createGeometry(
      new CorridorGeometry({
        vertexFormat: VertexFormat.POSITION_ONLY,
        positions: Cartesian3.fromDegreesArray([
          2.00571672577652,
          52.7781459942399,
          1.99188457974115,
          52.7764958852886,
          2.01325961458495,
          52.7674170680511,
          1.98708058340534,
          52.7733979856253,
          2.00634853946644,
          52.7650460748473,
        ]),
        cornerType: CornerType.BEVELED,
        width: 100,
      })
    );

    expect(m.attributes.position.values.length).toEqual(13 * 3); // 3 points * 3 corners + 2 points * 2 ends
    expect(m.indices.length).toEqual(11 * 3); // 4 segments * 2 triangles + 3 corners * 1 triangle
  });

  it("computes straight corridors", function () {
    var m = CorridorGeometry.createGeometry(
      new CorridorGeometry({
        vertexFormat: VertexFormat.POSITION_ONLY,
        positions: Cartesian3.fromDegreesArray([
          -67.655,
          0.0,
          -67.655,
          15.0,
          -67.655,
          20.0,
        ]),
        cornerType: CornerType.BEVELED,
        width: 400000,
        granularity: Math.PI / 6.0,
      })
    );

    expect(m.attributes.position.values.length).toEqual(4 * 3);
    expect(m.indices.length).toEqual(2 * 3);
  });

  it(
    "undefined is returned if there are less than two positions or the width is equal to " +
      "or less than zero",
    function () {
      var corridor0 = new CorridorGeometry({
        vertexFormat: VertexFormat.POSITION_ONLY,
        positions: Cartesian3.fromDegreesArray([-72.0, 35.0]),
        width: 100000,
      });
      var corridor1 = new CorridorGeometry({
        vertexFormat: VertexFormat.POSITION_ONLY,
        positions: Cartesian3.fromDegreesArray([
          -67.655,
          0.0,
          -67.655,
          15.0,
          -67.655,
          20.0,
        ]),
        width: 0,
      });
      var corridor2 = new CorridorGeometry({
        vertexFormat: VertexFormat.POSITION_ONLY,
        positions: Cartesian3.fromDegreesArray([
          -67.655,
          0.0,
          -67.655,
          15.0,
          -67.655,
          20.0,
        ]),
        width: -100,
      });

      var geometry0 = CorridorGeometry.createGeometry(corridor0);
      var geometry1 = CorridorGeometry.createGeometry(corridor1);
      var geometry2 = CorridorGeometry.createGeometry(corridor2);

      expect(geometry0).toBeUndefined();
      expect(geometry1).toBeUndefined();
      expect(geometry2).toBeUndefined();
    }
  );

  it("computing rectangle property", function () {
    var c = new CorridorGeometry({
      vertexFormat: VertexFormat.POSITION_ONLY,
      positions: Cartesian3.fromDegreesArray([
        -67.655,
        0.0,
        -67.655,
        15.0,
        -67.655,
        20.0,
      ]),
      cornerType: CornerType.MITERED,
      width: 1,
      granularity: Math.PI / 6.0,
    });

    var r = c.rectangle;
    expect(CesiumMath.toDegrees(r.north)).toEqualEpsilon(
      20.0,
      CesiumMath.EPSILON13
    );
    expect(CesiumMath.toDegrees(r.south)).toEqualEpsilon(
      0.0,
      CesiumMath.EPSILON20
    );
    expect(CesiumMath.toDegrees(r.east)).toEqual(-67.65499522658291);
    expect(CesiumMath.toDegrees(r.west)).toEqual(-67.6550047734171);
  });

  it("computeRectangle", function () {
    var options = {
      vertexFormat: VertexFormat.POSITION_ONLY,
      positions: Cartesian3.fromDegreesArray([
        -67.655,
        0.0,
        -67.655,
        15.0,
        -67.655,
        20.0,
      ]),
      cornerType: CornerType.MITERED,
      width: 1,
    };
    var geometry = new CorridorGeometry(options);

    var expected = geometry.rectangle;
    var result = CorridorGeometry.computeRectangle(options);

    expect(result).toEqual(expected);
  });

  it("computeRectangle with result parameter", function () {
    var options = {
      positions: Cartesian3.fromDegreesArray([
        72.0,
        0.0,
        85.0,
        15.0,
        83.0,
        20.0,
      ]),
      width: 5,
    };
    var geometry = new CorridorGeometry(options);

    var result = new Rectangle();
    var expected = geometry.rectangle;
    var returned = CorridorGeometry.computeRectangle(options, result);

    expect(returned).toEqual(expected);
    expect(returned).toBe(result);
  });

  it("computing textureCoordinateRotationPoints property", function () {
    var c = new CorridorGeometry({
      vertexFormat: VertexFormat.POSITION_ONLY,
      positions: Cartesian3.fromDegreesArray([
        -67.655,
        0.0,
        -67.655,
        15.0,
        -67.655,
        20.0,
      ]),
      cornerType: CornerType.MITERED,
      width: 1,
      granularity: Math.PI / 6.0,
    });

    // Corridors don't support geometry orientation or stRotation, so expect this to equal the original coordinate system.
    var textureCoordinateRotationPoints = c.textureCoordinateRotationPoints;
    expect(textureCoordinateRotationPoints.length).toEqual(6);
    expect(textureCoordinateRotationPoints[0]).toEqualEpsilon(
      0,
      CesiumMath.EPSILON7
    );
    expect(textureCoordinateRotationPoints[1]).toEqualEpsilon(
      0,
      CesiumMath.EPSILON7
    );
    expect(textureCoordinateRotationPoints[2]).toEqualEpsilon(
      0,
      CesiumMath.EPSILON7
    );
    expect(textureCoordinateRotationPoints[3]).toEqualEpsilon(
      1,
      CesiumMath.EPSILON7
    );
    expect(textureCoordinateRotationPoints[4]).toEqualEpsilon(
      1,
      CesiumMath.EPSILON7
    );
    expect(textureCoordinateRotationPoints[5]).toEqualEpsilon(
      0,
      CesiumMath.EPSILON7
    );
  });

  var positions = Cartesian3.fromDegreesArray([90.0, -30.0, 90.0, -31.0]);
  var corridor = new CorridorGeometry({
    vertexFormat: VertexFormat.POSITION_ONLY,
    positions: positions,
    cornerType: CornerType.BEVELED,
    width: 30000.0,
    granularity: 0.1,
  });

  var packedInstance = [
    2,
    positions[0].x,
    positions[0].y,
    positions[0].z,
    positions[1].x,
    positions[1].y,
    positions[1].z,
  ];
  packedInstance.push(
    Ellipsoid.WGS84.radii.x,
    Ellipsoid.WGS84.radii.y,
    Ellipsoid.WGS84.radii.z
  );
  packedInstance.push(1.0, 0.0, 0.0, 0.0, 0.0, 0.0);
  packedInstance.push(30000.0, 0.0, 0.0, 2.0, 0.1, 0.0, -1);
  createPackableSpecs(CorridorGeometry, corridor, packedInstance);
});
