import { Cartesian2 } from "../../Source/Cesium.js";
import { Cartesian3 } from "../../Source/Cesium.js";
import { Ellipsoid } from "../../Source/Cesium.js";
import { Math as CesiumMath } from "../../Source/Cesium.js";
import { PolygonPipeline } from "../../Source/Cesium.js";
import { WindingOrder } from "../../Source/Cesium.js";

describe("Core/PolygonPipeline", function () {
  beforeEach(function () {
    CesiumMath.setRandomNumberSeed(0.0);
  });

  it("computeArea2D computes a positive area", function () {
    var area = PolygonPipeline.computeArea2D([
      new Cartesian2(0.0, 0.0),
      new Cartesian2(2.0, 0.0),
      new Cartesian2(2.0, 1.0),
      new Cartesian2(0.0, 1.0),
    ]);

    expect(area).toEqual(2.0);
  });

  it("computeArea2D computes a negative area", function () {
    var area = PolygonPipeline.computeArea2D([
      new Cartesian2(0.0, 0.0),
      new Cartesian2(0.0, 2.0),
      new Cartesian2(1.0, 2.0),
      new Cartesian2(1.0, 0.0),
    ]);

    expect(area).toEqual(-2.0);
  });

  it("computeArea2D throws without positions", function () {
    expect(function () {
      PolygonPipeline.computeArea2D();
    }).toThrowDeveloperError();
  });

  it("computeArea2D throws without three positions", function () {
    expect(function () {
      PolygonPipeline.computeArea2D([Cartesian3.ZERO, Cartesian3.ZERO]);
    }).toThrowDeveloperError();
  });

  ///////////////////////////////////////////////////////////////////////

  it("computeWindingOrder2D computes counter-clockwise", function () {
    var area = PolygonPipeline.computeWindingOrder2D([
      new Cartesian2(0.0, 0.0),
      new Cartesian2(2.0, 0.0),
      new Cartesian2(2.0, 1.0),
      new Cartesian2(0.0, 1.0),
    ]);

    expect(area).toEqual(WindingOrder.COUNTER_CLOCKWISE);
  });

  it("computeWindingOrder2D computes clockwise", function () {
    var area = PolygonPipeline.computeWindingOrder2D([
      new Cartesian2(0.0, 0.0),
      new Cartesian2(0.0, 2.0),
      new Cartesian2(1.0, 2.0),
      new Cartesian2(1.0, 0.0),
    ]);

    expect(area).toEqual(WindingOrder.CLOCKWISE);
  });

  it("computeWindingOrder2D throws without positions", function () {
    expect(function () {
      PolygonPipeline.computeWindingOrder2D();
    }).toThrowDeveloperError();
  });

  it("computeWindingOrder2D throws without three positions", function () {
    expect(function () {
      PolygonPipeline.computeWindingOrder2D([Cartesian3.ZERO, Cartesian3.ZERO]);
    }).toThrowDeveloperError();
  });

  describe("triangulate", function () {
    // Test integration with earcut.js
    // The package is tested independently. See https://github.com/mapbox/earcut

    it("throws without positions", function () {
      expect(function () {
        PolygonPipeline.triangulate(undefined, []);
      }).toThrowDeveloperError();
    });

    it("a triangle", function () {
      var positions = [
        new Cartesian2(0.0, 0.0),
        new Cartesian2(1.0, 0.0),
        new Cartesian2(0.0, 1.0),
      ];
      var indices = PolygonPipeline.triangulate(positions, []);
      expect(indices).toEqual([1, 2, 0]);
    });

    it("a square", function () {
      var positions = [
        new Cartesian2(0.0, 0.0),
        new Cartesian2(1.0, 0.0),
        new Cartesian2(1.0, 1.0),
        new Cartesian2(0.0, 1.0),
      ];
      var indices = PolygonPipeline.triangulate(positions, []);
      expect(indices).toEqual([2, 3, 0, 0, 1, 2]);
    });

    it("eliminates holes", function () {
      var positions = [
        new Cartesian2(0.0, 0.0),
        new Cartesian2(3.0, 0.0),
        new Cartesian2(3.0, 3.0),
        new Cartesian2(0.0, 3.0),
      ];
      var hole = [
        new Cartesian2(1.0, 1.0),
        new Cartesian2(2.0, 1.0),
        new Cartesian2(2.0, 2.0),
        new Cartesian2(1.0, 2.0),
      ];

      var combinedPositions = positions.concat(hole);
      var indices = PolygonPipeline.triangulate(combinedPositions, [4]);

      expect(indices).toEqual([
        3,
        0,
        4,
        5,
        4,
        0,
        3,
        4,
        7,
        5,
        0,
        1,
        2,
        3,
        7,
        6,
        5,
        1,
        2,
        7,
        6,
        6,
        1,
        2,
      ]);
    });

    it("eliminates multiple holes", function () {
      var positions = [
        new Cartesian2(0.0, 0.0),
        new Cartesian2(3.0, 0.0),
        new Cartesian2(3.0, 5.0),
        new Cartesian2(0.0, 5.0),
      ];
      var bottomHole = [
        new Cartesian2(1.0, 1.0),
        new Cartesian2(2.0, 1.0),
        new Cartesian2(2.0, 2.0),
        new Cartesian2(1.0, 2.0),
      ];
      var topHole = [
        new Cartesian2(1.0, 3.0),
        new Cartesian2(2.0, 3.0),
        new Cartesian2(2.0, 4.0),
        new Cartesian2(1.0, 4.0),
      ];

      var combinedPositions = positions.concat(bottomHole).concat(topHole);
      var indices = PolygonPipeline.triangulate(combinedPositions, [4, 8]);

      expect(indices).toEqual([
        0,
        8,
        11,
        0,
        4,
        7,
        5,
        4,
        0,
        3,
        0,
        11,
        8,
        0,
        7,
        5,
        0,
        1,
        2,
        3,
        11,
        9,
        8,
        7,
        6,
        5,
        1,
        2,
        11,
        10,
        9,
        7,
        6,
        6,
        1,
        2,
        2,
        10,
        9,
        9,
        6,
        2,
      ]);
    });
  });

  ///////////////////////////////////////////////////////////////////////

  it("computeSubdivision throws without ellipsoid", function () {
    expect(function () {
      PolygonPipeline.computeSubdivision();
    }).toThrowDeveloperError();
  });

  it("computeSubdivision throws without positions", function () {
    expect(function () {
      PolygonPipeline.computeSubdivision(Ellipsoid.WGS84);
    }).toThrowDeveloperError();
  });

  it("computeSubdivision throws without indices", function () {
    expect(function () {
      PolygonPipeline.computeSubdivision(Ellipsoid.WGS84, []);
    }).toThrowDeveloperError();
  });

  it("computeSubdivision throws with less than 3 indices", function () {
    expect(function () {
      PolygonPipeline.computeSubdivision(Ellipsoid.WGS84, [], [1, 2]);
    }).toThrowDeveloperError();
  });

  it("computeSubdivision throws without a multiple of 3 indices", function () {
    expect(function () {
      PolygonPipeline.computeSubdivision(Ellipsoid.WGS84, [], [1, 2, 3, 4]);
    }).toThrowDeveloperError();
  });

  it("computeSubdivision throws with negative granularity", function () {
    expect(function () {
      PolygonPipeline.computeSubdivision(Ellipsoid.WGS84, [], [1, 2, 3], -1.0);
    }).toThrowDeveloperError();
  });

  it("computeSubdivision", function () {
    var positions = [
      new Cartesian3(0.0, 0.0, 90.0),
      new Cartesian3(0.0, 90.0, 0.0),
      new Cartesian3(90.0, 0.0, 0.0),
    ];
    var indices = [0, 1, 2];
    var subdivision = PolygonPipeline.computeSubdivision(
      Ellipsoid.WGS84,
      positions,
      indices,
      60.0
    );

    expect(subdivision.attributes.position.values[0]).toEqual(0.0);
    expect(subdivision.attributes.position.values[1]).toEqual(0.0);
    expect(subdivision.attributes.position.values[2]).toEqual(90.0);
    expect(subdivision.attributes.position.values[3]).toEqual(0.0);
    expect(subdivision.attributes.position.values[4]).toEqual(90.0);
    expect(subdivision.attributes.position.values[5]).toEqual(0.0);
    expect(subdivision.attributes.position.values[6]).toEqual(90.0);
    expect(subdivision.attributes.position.values[7]).toEqual(0.0);
    expect(subdivision.attributes.position.values[8]).toEqual(0.0);

    expect(subdivision.indices[0]).toEqual(0);
    expect(subdivision.indices[1]).toEqual(1);
    expect(subdivision.indices[2]).toEqual(2);
  });

  ///////////////////////////////////////////////////////////////////////

  it("computeRhumbLineSubdivision throws without ellipsoid", function () {
    expect(function () {
      PolygonPipeline.computeRhumbLineSubdivision();
    }).toThrowDeveloperError();
  });

  it("computeRhumbLineSubdivision throws without positions", function () {
    expect(function () {
      PolygonPipeline.computeRhumbLineSubdivision(Ellipsoid.WGS84);
    }).toThrowDeveloperError();
  });

  it("computeRhumbLineSubdivision throws without indices", function () {
    expect(function () {
      PolygonPipeline.computeRhumbLineSubdivision(Ellipsoid.WGS84, []);
    }).toThrowDeveloperError();
  });

  it("computeRhumbLineSubdivision throws with less than 3 indices", function () {
    expect(function () {
      PolygonPipeline.computeRhumbLineSubdivision(Ellipsoid.WGS84, [], [1, 2]);
    }).toThrowDeveloperError();
  });

  it("computeRhumbLineSubdivision throws without a multiple of 3 indices", function () {
    expect(function () {
      PolygonPipeline.computeRhumbLineSubdivision(
        Ellipsoid.WGS84,
        [],
        [1, 2, 3, 4]
      );
    }).toThrowDeveloperError();
  });

  it("computeRhumbLineSubdivision throws with negative granularity", function () {
    expect(function () {
      PolygonPipeline.computeRhumbLineSubdivision(
        Ellipsoid.WGS84,
        [],
        [1, 2, 3],
        -1.0
      );
    }).toThrowDeveloperError();
  });

  it("computeRhumbLineSubdivision", function () {
    var positions = Cartesian3.fromDegreesArray([0, 0, 1, 0, 1, 1]);
    var indices = [0, 1, 2];
    var subdivision = PolygonPipeline.computeRhumbLineSubdivision(
      Ellipsoid.WGS84,
      positions,
      indices,
      2 * CesiumMath.RADIANS_PER_DEGREE
    );

    expect(subdivision.attributes.position.values[0]).toEqual(positions[0].x);
    expect(subdivision.attributes.position.values[1]).toEqual(positions[0].y);
    expect(subdivision.attributes.position.values[2]).toEqual(positions[0].y);
    expect(subdivision.attributes.position.values[3]).toEqual(positions[1].x);
    expect(subdivision.attributes.position.values[4]).toEqual(positions[1].y);
    expect(subdivision.attributes.position.values[5]).toEqual(positions[1].z);
    expect(subdivision.attributes.position.values[6]).toEqual(positions[2].x);
    expect(subdivision.attributes.position.values[7]).toEqual(positions[2].y);
    expect(subdivision.attributes.position.values[8]).toEqual(positions[2].z);

    expect(subdivision.indices[0]).toEqual(0);
    expect(subdivision.indices[1]).toEqual(1);
    expect(subdivision.indices[2]).toEqual(2);
  });

  it("computeRhumbLineSubdivision with subdivisions", function () {
    var positions = Cartesian3.fromDegreesArray([0, 0, 1, 0, 1, 1]);
    var indices = [0, 1, 2];
    var subdivision = PolygonPipeline.computeRhumbLineSubdivision(
      Ellipsoid.WGS84,
      positions,
      indices,
      0.5 * CesiumMath.RADIANS_PER_DEGREE
    );

    expect(subdivision.attributes.position.values.length).toEqual(36); // 12 vertices
    expect(subdivision.indices.length).toEqual(36); // 12 triangles
  });

  it("computeRhumbLineSubdivision with subdivisions across the IDL", function () {
    var positions = Cartesian3.fromDegreesArray([178, 0, -178, 0, -178, 1]);
    var indices = [0, 1, 2];
    var subdivision = PolygonPipeline.computeRhumbLineSubdivision(
      Ellipsoid.WGS84,
      positions,
      indices,
      0.5 * CesiumMath.RADIANS_PER_DEGREE
    );

    expect(subdivision.attributes.position.values.length).toEqual(180); // 60 vertices
    expect(subdivision.indices.length).toEqual(252); // 84 triangles
  });
});
