import { arrayFill } from "../../Source/Cesium.js";
import { Cartesian3 } from "../../Source/Cesium.js";
import { EllipseGeometry } 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/EllipseGeometry", function () {
  it("throws without a center", function () {
    expect(function () {
      return new EllipseGeometry({
        semiMajorAxis: 1.0,
        semiMinorAxis: 1.0,
      });
    }).toThrowDeveloperError();
  });

  it("throws without a semiMajorAxis", function () {
    expect(function () {
      return new EllipseGeometry({
        center: Cartesian3.fromDegrees(0, 0),
        semiMinorAxis: 1.0,
      });
    }).toThrowDeveloperError();
  });

  it("throws without a semiMinorAxis", function () {
    expect(function () {
      return new EllipseGeometry({
        center: Cartesian3.fromDegrees(0, 0),
        semiMajorAxis: 1.0,
      });
    }).toThrowDeveloperError();
  });

  it("throws with a negative granularity", function () {
    expect(function () {
      return new EllipseGeometry({
        center: Cartesian3.fromDegrees(0, 0),
        semiMajorAxis: 1.0,
        semiMinorAxis: 1.0,
        granularity: -1.0,
      });
    }).toThrowDeveloperError();
  });

  it("throws when semiMajorAxis is less than the semiMajorAxis", function () {
    expect(function () {
      return new EllipseGeometry({
        center: Cartesian3.fromDegrees(0, 0),
        semiMajorAxis: 1.0,
        semiMinorAxis: 2.0,
      });
    }).toThrowDeveloperError();
  });

  it("computes positions", function () {
    var m = EllipseGeometry.createGeometry(
      new EllipseGeometry({
        vertexFormat: VertexFormat.POSITION_ONLY,
        ellipsoid: Ellipsoid.WGS84,
        center: Cartesian3.fromDegrees(0, 0),
        granularity: 0.1,
        semiMajorAxis: 1.0,
        semiMinorAxis: 1.0,
      })
    );

    expect(m.attributes.position.values.length).toEqual(16 * 3); // rows of 1 + 4 + 6 + 4 + 1
    expect(m.indices.length).toEqual(22 * 3); // rows of 3 + 8 + 8 + 3
    expect(m.boundingSphere.radius).toEqual(1);
  });

  it("compute all vertex attributes", function () {
    var m = EllipseGeometry.createGeometry(
      new EllipseGeometry({
        vertexFormat: VertexFormat.ALL,
        ellipsoid: Ellipsoid.WGS84,
        center: Cartesian3.fromDegrees(0, 0),
        granularity: 0.1,
        semiMajorAxis: 1.0,
        semiMinorAxis: 1.0,
      })
    );

    var numVertices = 16;
    var numTriangles = 22;
    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("compute texture coordinates with rotation", function () {
    var m = EllipseGeometry.createGeometry(
      new EllipseGeometry({
        vertexFormat: VertexFormat.POSITION_AND_ST,
        ellipsoid: Ellipsoid.WGS84,
        center: Cartesian3.fromDegrees(0, 0),
        granularity: 0.1,
        semiMajorAxis: 1.0,
        semiMinorAxis: 1.0,
        stRotation: CesiumMath.PI_OVER_TWO,
      })
    );

    var positions = m.attributes.position.values;
    var st = m.attributes.st.values;
    var length = st.length;

    var numVertices = 16;
    var numTriangles = 22;
    expect(positions.length).toEqual(numVertices * 3);
    expect(length).toEqual(numVertices * 2);
    expect(m.indices.length).toEqual(numTriangles * 3);

    expect(st[length - 2]).toEqualEpsilon(0.5, CesiumMath.EPSILON2);
    expect(st[length - 1]).toEqualEpsilon(0.0, CesiumMath.EPSILON2);
  });

  it("computes positions extruded", function () {
    var m = EllipseGeometry.createGeometry(
      new EllipseGeometry({
        vertexFormat: VertexFormat.POSITION_ONLY,
        ellipsoid: Ellipsoid.WGS84,
        center: Cartesian3.fromDegrees(0, 0),
        granularity: 0.1,
        semiMajorAxis: 1.0,
        semiMinorAxis: 1.0,
        extrudedHeight: 50000,
      })
    );

    var numVertices = 48; // 16 top + 16 bottom + 8 top edge + 8 bottom edge
    var numTriangles = 60; // 22 top fill + 22 bottom fill + 2 triangles * 8 sides
    expect(m.attributes.position.values.length).toEqual(numVertices * 3);
    expect(m.indices.length).toEqual(numTriangles * 3);
  });

  it("computes offset attribute", function () {
    var m = EllipseGeometry.createGeometry(
      new EllipseGeometry({
        vertexFormat: VertexFormat.POSITION_ONLY,
        ellipsoid: Ellipsoid.WGS84,
        center: Cartesian3.fromDegrees(0, 0),
        granularity: 0.1,
        semiMajorAxis: 1.0,
        semiMinorAxis: 1.0,
        offsetAttribute: GeometryOffsetAttribute.TOP,
      })
    );

    var numVertices = 16;
    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 = EllipseGeometry.createGeometry(
      new EllipseGeometry({
        vertexFormat: VertexFormat.POSITION_ONLY,
        ellipsoid: Ellipsoid.WGS84,
        center: Cartesian3.fromDegrees(0, 0),
        granularity: 0.1,
        semiMajorAxis: 1.0,
        semiMinorAxis: 1.0,
        extrudedHeight: 50000,
        offsetAttribute: GeometryOffsetAttribute.TOP,
      })
    );

    var numVertices = 48;
    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, 16);
    expected = arrayFill(expected, 1, 32, 40);
    expect(offset).toEqual(expected);
  });

  it("computes offset attribute extruded for all vertices", function () {
    var m = EllipseGeometry.createGeometry(
      new EllipseGeometry({
        vertexFormat: VertexFormat.POSITION_ONLY,
        ellipsoid: Ellipsoid.WGS84,
        center: Cartesian3.fromDegrees(0, 0),
        granularity: 0.1,
        semiMajorAxis: 1.0,
        semiMinorAxis: 1.0,
        extrudedHeight: 50000,
        offsetAttribute: GeometryOffsetAttribute.ALL,
      })
    );

    var numVertices = 48;
    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("compute all vertex attributes extruded", function () {
    var m = EllipseGeometry.createGeometry(
      new EllipseGeometry({
        vertexFormat: VertexFormat.ALL,
        ellipsoid: Ellipsoid.WGS84,
        center: Cartesian3.fromDegrees(0, 0),
        granularity: 0.1,
        semiMajorAxis: 1.0,
        semiMinorAxis: 1.0,
        extrudedHeight: 50000,
      })
    );

    var numVertices = 48;
    var numTriangles = 60;
    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("undefined is returned if the minor axis is equal to or less than zero", function () {
    var ellipse0 = new EllipseGeometry({
      center: Cartesian3.fromDegrees(-75.59777, 40.03883),
      semiMajorAxis: 300000.0,
      semiMinorAxis: 0.0,
    });
    var ellipse1 = new EllipseGeometry({
      center: Cartesian3.fromDegrees(-75.59777, 40.03883),
      semiMajorAxis: 0.0,
      semiMinorAxis: -1.0,
    });
    var ellipse2 = new EllipseGeometry({
      center: Cartesian3.fromDegrees(-75.59777, 40.03883),
      semiMajorAxis: 300000.0,
      semiMinorAxis: -10.0,
    });
    var ellipse3 = new EllipseGeometry({
      center: Cartesian3.fromDegrees(-75.59777, 40.03883),
      semiMajorAxis: -1.0,
      semiMinorAxis: -2.0,
    });

    var geometry0 = EllipseGeometry.createGeometry(ellipse0);
    var geometry1 = EllipseGeometry.createGeometry(ellipse1);
    var geometry2 = EllipseGeometry.createGeometry(ellipse2);
    var geometry3 = EllipseGeometry.createGeometry(ellipse3);

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

  it("createShadowVolume uses properties from geometry", function () {
    var m = new EllipseGeometry({
      center: Cartesian3.fromDegrees(-75.59777, 40.03883),
      semiMajorAxis: 3000.0,
      semiMinorAxis: 1500.0,
      ellipsoid: Ellipsoid.WGS84,
      rotation: CesiumMath.PI_OVER_TWO,
      stRotation: CesiumMath.PI_OVER_FOUR,
      granularity: 10000,
      extrudedHeight: 0,
      height: 100,
      vertexFormat: VertexFormat.ALL,
    });

    var minHeightFunc = function () {
      return 100;
    };

    var maxHeightFunc = function () {
      return 1000;
    };

    var sv = EllipseGeometry.createShadowVolume(
      m,
      minHeightFunc,
      maxHeightFunc
    );

    expect(sv._center.equals(m._center)).toBe(true);
    expect(sv._semiMajorAxis).toBe(m._semiMajorAxis);
    expect(sv._semiMinorAxis).toBe(m._semiMinorAxis);
    expect(sv._ellipsoid.equals(m._ellipsoid)).toBe(true);
    expect(sv._rotation).toBe(m._rotation);
    expect(sv._stRotation).toBe(m._stRotation);
    expect(sv._granularity).toBe(m._granularity);
    expect(sv._extrudedHeight).toBe(minHeightFunc());
    expect(sv._height).toBe(maxHeightFunc());

    expect(sv._vertexFormat.bitangent).toBe(
      VertexFormat.POSITION_ONLY.bitangent
    );
    expect(sv._vertexFormat.color).toBe(VertexFormat.POSITION_ONLY.color);
    expect(sv._vertexFormat.normal).toBe(VertexFormat.POSITION_ONLY.normal);
    expect(sv._vertexFormat.position).toBe(VertexFormat.POSITION_ONLY.position);
    expect(sv._vertexFormat.st).toBe(VertexFormat.POSITION_ONLY.st);
    expect(sv._vertexFormat.tangent).toBe(VertexFormat.POSITION_ONLY.tangent);
  });

  it("computing rectangle property", function () {
    var center = Cartesian3.fromDegrees(-75.59777, 40.03883);
    var ellipse = new EllipseGeometry({
      center: center,
      semiMajorAxis: 2000.0,
      semiMinorAxis: 1000.0,
    });

    var r = ellipse.rectangle;
    expect(r.north).toEqualEpsilon(0.6989665987920752, CesiumMath.EPSILON7);
    expect(r.south).toEqualEpsilon(0.6986522252554146, CesiumMath.EPSILON7);
    expect(r.east).toEqualEpsilon(-1.3190209903056758, CesiumMath.EPSILON7);
    expect(r.west).toEqualEpsilon(-1.3198389970251112, CesiumMath.EPSILON7);

    // Polar ellipse
    center = Cartesian3.fromDegrees(0.0, 90);
    ellipse = new EllipseGeometry({
      center: center,
      semiMajorAxis: 2000.0,
      semiMinorAxis: 1000.0,
    });

    r = ellipse.rectangle;
    expect(r.north).toEqualEpsilon(
      CesiumMath.PI_OVER_TWO - CesiumMath.EPSILON7,
      CesiumMath.EPSILON7
    );
    expect(r.south).toEqualEpsilon(1.570483806950967, CesiumMath.EPSILON7);
    expect(r.east).toEqualEpsilon(CesiumMath.PI, CesiumMath.EPSILON7);
    expect(r.west).toEqualEpsilon(-CesiumMath.PI, CesiumMath.EPSILON7);
  });

  it("computeRectangle", function () {
    var options = {
      center: Cartesian3.fromDegrees(-30, 33),
      semiMajorAxis: 2000.0,
      semiMinorAxis: 1000.0,
      rotation: CesiumMath.PI_OVER_TWO,
      granularity: 0.5,
      ellipsoid: Ellipsoid.UNIT_SPHERE,
    };
    var geometry = new EllipseGeometry(options);

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

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

  it("computeRectangle with result parameter", function () {
    var options = {
      center: Cartesian3.fromDegrees(30, -33),
      semiMajorAxis: 500.0,
      semiMinorAxis: 200.0,
    };
    var geometry = new EllipseGeometry(options);

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

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

  it("computing textureCoordinateRotationPoints property", function () {
    var center = Cartesian3.fromDegrees(0, 0);
    var ellipse = new EllipseGeometry({
      center: center,
      semiMajorAxis: 2000.0,
      semiMinorAxis: 1000.0,
      stRotation: CesiumMath.toRadians(90),
    });

    // 90 degree rotation means (0, 1) should be the new min and (1, 1) (0, 0) are extents
    var textureCoordinateRotationPoints =
      ellipse.textureCoordinateRotationPoints;
    expect(textureCoordinateRotationPoints.length).toEqual(6);
    expect(textureCoordinateRotationPoints[0]).toEqualEpsilon(
      0,
      CesiumMath.EPSILON7
    );
    expect(textureCoordinateRotationPoints[1]).toEqualEpsilon(
      1,
      CesiumMath.EPSILON7
    );
    expect(textureCoordinateRotationPoints[2]).toEqualEpsilon(
      1,
      CesiumMath.EPSILON7
    );
    expect(textureCoordinateRotationPoints[3]).toEqualEpsilon(
      1,
      CesiumMath.EPSILON7
    );
    expect(textureCoordinateRotationPoints[4]).toEqualEpsilon(
      0,
      CesiumMath.EPSILON7
    );
    expect(textureCoordinateRotationPoints[5]).toEqualEpsilon(
      0,
      CesiumMath.EPSILON7
    );

    ellipse = new EllipseGeometry({
      center: center,
      semiMajorAxis: 2000.0,
      semiMinorAxis: 1000.0,
      stRotation: CesiumMath.toRadians(0),
    });

    textureCoordinateRotationPoints = ellipse.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 center = Cartesian3.fromDegrees(0, 0);
  var ellipsoid = Ellipsoid.WGS84;
  var packableInstance = new EllipseGeometry({
    vertexFormat: VertexFormat.POSITION_AND_ST,
    ellipsoid: ellipsoid,
    center: center,
    granularity: 0.1,
    semiMajorAxis: 1.0,
    semiMinorAxis: 1.0,
    stRotation: CesiumMath.PI_OVER_TWO,
  });
  var packedInstance = [
    center.x,
    center.y,
    center.z,
    ellipsoid.radii.x,
    ellipsoid.radii.y,
    ellipsoid.radii.z,
    1.0,
    0.0,
    1.0,
    0.0,
    0.0,
    0.0,
    1.0,
    1.0,
    0.0,
    CesiumMath.PI_OVER_TWO,
    0.0,
    0.1,
    0.0,
    0.0,
    -1,
  ];
  createPackableSpecs(EllipseGeometry, packableInstance, packedInstance);
});
