import { Cartesian2 } from "../../Source/Cesium.js";
import { Cartesian3 } from "../../Source/Cesium.js";
import { Cartographic } from "../../Source/Cesium.js";
import { clone } from "../../Source/Cesium.js";
import { Color } from "../../Source/Cesium.js";
import { DistanceDisplayCondition } from "../../Source/Cesium.js";
import { Ellipsoid } from "../../Source/Cesium.js";
import { Math as CesiumMath } from "../../Source/Cesium.js";
import { NearFarScalar } from "../../Source/Cesium.js";
import { Rectangle } from "../../Source/Cesium.js";
import { Cesium3DTileBatchTable } from "../../Source/Cesium.js";
import { Cesium3DTileStyle } from "../../Source/Cesium.js";
import { ColorBlendMode } from "../../Source/Cesium.js";
import { HorizontalOrigin } from "../../Source/Cesium.js";
import { LabelStyle } from "../../Source/Cesium.js";
import { Vector3DTilePoints } from "../../Source/Cesium.js";
import { VerticalOrigin } from "../../Source/Cesium.js";
import createScene from "../createScene.js";
import pollToPromise from "../pollToPromise.js";

describe(
  "Scene/Vector3DTilePoints",
  function () {
    var scene;
    var rectangle;
    var points;

    var ellipsoid = Ellipsoid.WGS84;

    beforeAll(function () {
      scene = createScene();
    });

    afterAll(function () {
      scene.destroyForSpecs();
    });

    var mockTileset = {
      _statistics: {
        texturesByteLength: 0,
      },
      tileset: {
        _statistics: {
          batchTableByteLength: 0,
        },
        colorBlendMode: ColorBlendMode.HIGHLIGHT,
        ellipsoid: Ellipsoid.WGS84,
      },
      tile: {},
      getFeature: function (id) {
        return { batchId: id };
      },
    };

    beforeEach(function () {
      rectangle = Rectangle.fromDegrees(-40.0, -40.0, 40.0, 40.0);
    });

    afterEach(function () {
      scene.primitives.removeAll();
      points = points && !points.isDestroyed() && points.destroy();
    });

    function loadPoints(points) {
      var ready = false;
      points.readyPromise.then(function () {
        ready = true;
      });
      return pollToPromise(function () {
        points.update(scene.frameState);
        scene.frameState.commandList.length = 0;
        return ready;
      });
    }

    function zigZag(value) {
      return ((value << 1) ^ (value >> 15)) & 0xffff;
    }

    var maxShort = 32767;

    function encodePositions(
      rectangle,
      minimumHeight,
      maximumHeight,
      positions
    ) {
      var length = positions.length;
      var buffer = new Uint16Array(length * 3);

      var lastU = 0;
      var lastV = 0;
      var lastH = 0;

      for (var i = 0; i < length; ++i) {
        var position = positions[i];

        var u = (position.longitude - rectangle.west) / rectangle.width;
        var v = (position.latitude - rectangle.south) / rectangle.height;
        var h =
          (position.height - minimumHeight) / (maximumHeight - minimumHeight);

        u = CesiumMath.clamp(u, 0.0, 1.0);
        v = CesiumMath.clamp(v, 0.0, 1.0);
        h = CesiumMath.clamp(h, 0.0, 1.0);

        u = Math.floor(u * maxShort);
        v = Math.floor(v * maxShort);
        h = Math.floor(h * maxShort);

        buffer[i] = zigZag(u - lastU);
        buffer[i + length] = zigZag(v - lastV);
        buffer[i + length * 2] = zigZag(h - lastH);

        lastU = u;
        lastV = v;
        lastH = h;
      }

      return buffer;
    }

    it("renders a point", function () {
      var minHeight = 0.0;
      var maxHeight = 100.0;
      var cartoPositions = [Cartographic.fromDegrees(0.0, 0.0, 10.0)];
      var positions = encodePositions(
        rectangle,
        minHeight,
        maxHeight,
        cartoPositions
      );

      var batchTable = new Cesium3DTileBatchTable(mockTileset, 1);
      batchTable.update(mockTileset, scene.frameState);

      points = scene.primitives.add(
        new Vector3DTilePoints({
          positions: positions,
          batchTable: batchTable,
          batchIds: new Uint16Array([0]),
          rectangle: rectangle,
          minimumHeight: minHeight,
          maximumHeight: maxHeight,
        })
      );
      return loadPoints(points).then(function () {
        var features = [];
        points.createFeatures(mockTileset, features);
        points.applyStyle(undefined, features);

        scene.camera.lookAt(
          Cartesian3.fromDegrees(0.0, 0.0, 30.0),
          new Cartesian3(0.0, 0.0, 50.0)
        );
        expect(scene).toRender([255, 255, 255, 255]);
      });
    });

    it("renders multiple points", function () {
      var minHeight = 0.0;
      var maxHeight = 100.0;
      var cartoPositions = [
        Cartographic.fromDegrees(0.0, 0.0, 10.0),
        Cartographic.fromDegrees(5.0, 0.0, 20.0),
        Cartographic.fromDegrees(-5.0, 0.0, 1.0),
        Cartographic.fromDegrees(0.0, 6.0, 5.0),
        Cartographic.fromDegrees(0.0, -6.0, 90.0),
      ];
      var positions = encodePositions(
        rectangle,
        minHeight,
        maxHeight,
        cartoPositions
      );

      var batchTable = new Cesium3DTileBatchTable(mockTileset, 5);
      batchTable.update(mockTileset, scene.frameState);

      points = scene.primitives.add(
        new Vector3DTilePoints({
          positions: positions,
          batchTable: batchTable,
          batchIds: new Uint16Array([0, 1, 2, 3, 4]),
          rectangle: rectangle,
          minimumHeight: minHeight,
          maximumHeight: maxHeight,
        })
      );
      var style = new Cesium3DTileStyle({
        verticalOrigin: VerticalOrigin.BOTTOM,
      });
      return loadPoints(points).then(function () {
        var features = [];
        points.createFeatures(mockTileset, features);
        points.applyStyle(style, features);

        for (var i = 0; i < cartoPositions.length; ++i) {
          var position = ellipsoid.cartographicToCartesian(cartoPositions[i]);
          scene.camera.lookAt(position, new Cartesian3(0.0, 0.0, 50.0));
          expect(scene).toRenderAndCall(function (rgba) {
            expect(rgba[0]).toBeGreaterThan(0);
            expect(rgba[0]).toEqual(rgba[1]);
            expect(rgba[0]).toEqual(rgba[2]);
            expect(rgba[3]).toEqual(255);
          });
        }
      });
    });

    it("picks a point", function () {
      var minHeight = 0.0;
      var maxHeight = 100.0;
      var cartoPositions = [Cartographic.fromDegrees(0.0, 0.0, 10.0)];
      var positions = encodePositions(
        rectangle,
        minHeight,
        maxHeight,
        cartoPositions
      );

      var batchTable = new Cesium3DTileBatchTable(mockTileset, 1);

      points = scene.primitives.add(
        new Vector3DTilePoints({
          positions: positions,
          batchTable: batchTable,
          batchIds: new Uint16Array([0]),
          rectangle: rectangle,
          minimumHeight: minHeight,
          maximumHeight: maxHeight,
        })
      );
      return loadPoints(points).then(function () {
        scene.camera.lookAt(
          Cartesian3.fromDegrees(0.0, 0.0, 10.0),
          new Cartesian3(0.0, 0.0, 50.0)
        );

        var features = [];
        points.createFeatures(mockTileset, features);
        points.applyStyle(undefined, features);

        var getFeature = mockTileset.getFeature;
        mockTileset.getFeature = function (index) {
          return features[index];
        };

        scene.frameState.passes.pick = true;
        batchTable.update(mockTileset, scene.frameState);
        expect(scene).toPickAndCall(function (result) {
          expect(result).toBe(features[0]);
        });
        scene.frameState.passes.pick = false;

        mockTileset.getFeature = getFeature;
      });
    });

    it("renders multiple points with style", function () {
      var minHeight = 0.0;
      var maxHeight = 100.0;
      var cartoPositions = [
        Cartographic.fromDegrees(0.0, 0.0, 10.0),
        Cartographic.fromDegrees(5.0, 0.0, 20.0),
        Cartographic.fromDegrees(-5.0, 0.0, 1.0),
        Cartographic.fromDegrees(0.0, 6.0, 5.0),
        Cartographic.fromDegrees(0.0, -6.0, 90.0),
      ];
      var positions = encodePositions(
        rectangle,
        minHeight,
        maxHeight,
        cartoPositions
      );

      var mockTilesetClone = clone(mockTileset);
      var batchTable = new Cesium3DTileBatchTable(mockTilesetClone, 5);
      mockTilesetClone.batchTable = batchTable;

      for (var i = 0; i < 5; ++i) {
        batchTable.setProperty(i, "temperature", i);
      }
      batchTable.update(mockTilesetClone, scene.frameState);

      points = scene.primitives.add(
        new Vector3DTilePoints({
          positions: positions,
          batchTable: batchTable,
          batchIds: new Uint16Array([0, 1, 2, 3, 4]),
          rectangle: rectangle,
          minimumHeight: minHeight,
          maximumHeight: maxHeight,
        })
      );

      var style = new Cesium3DTileStyle({
        show: "true",
        pointSize: "10.0",
        color: "rgba(255, 255, 0, 0.5)",
        pointOutlineColor: "rgba(255, 255, 0, 1.0)",
        pointOutlineWidth: "11.0 * ${temperature}",
        labelColor: "rgba(255, 255, 0, 1.0)",
        labelOutlineColor: "rgba(255, 255, 0, 0.5)",
        labelOutlineWidth: "1.0",
        font: '"30px sans-serif"',
        labelStyle: "" + LabelStyle.FILL_AND_OUTLINE,
        labelText: '"test"',
        backgroundColor: "rgba(255, 255, 0, 0.2)",
        backgroundPadding: "vec2(10, 11)",
        backgroundEnabled: "true",
        scaleByDistance: "vec4(1.0e4, 1.0, 1.0e6, 0.0)",
        translucencyByDistance: "vec4(1.0e4, 1.0, 1.0e6, 0.0)",
        distanceDisplayCondition: "vec2(0.1, 1.0e6)",
        heightOffset: "0.0",
        anchorLineEnabled: "true",
        anchorLineColor: "rgba(255, 255, 0, 1.0)",
        disableDepthTestDistance: "1.0e6",
        horizontalOrigin: "" + HorizontalOrigin.CENTER,
        verticalOrigin: "" + VerticalOrigin.CENTER,
        labelHorizontalOrigin: "" + HorizontalOrigin.RIGHT,
        labelVerticalOrigin: "" + VerticalOrigin.BOTTOM,
      });

      return loadPoints(points).then(function () {
        var features = [];
        points.createFeatures(mockTilesetClone, features);
        points.applyStyle(style, features);

        var i;
        for (i = 0; i < features.length; ++i) {
          var feature = features[i];
          expect(feature.show).toEqual(true);
          expect(feature.pointSize).toEqual(10.0);
          expect(feature.color).toEqual(new Color(1.0, 1.0, 0.0, 0.5));
          expect(feature.pointOutlineColor).toEqual(
            new Color(1.0, 1.0, 0.0, 1.0)
          );
          expect(feature.pointOutlineWidth).toEqual(11.0 * i);
          expect(feature.labelColor).toEqual(new Color(1.0, 1.0, 0.0, 1.0));
          expect(feature.labelOutlineColor).toEqual(
            new Color(1.0, 1.0, 0.0, 0.5)
          );
          expect(feature.labelOutlineWidth).toEqual(1.0);
          expect(feature.font).toEqual("30px sans-serif");
          expect(feature.labelStyle).toEqual(LabelStyle.FILL_AND_OUTLINE);
          expect(feature.labelText).toEqual("test");
          expect(feature.backgroundColor).toEqual(
            new Color(1.0, 1.0, 0.0, 0.2)
          );
          expect(feature.backgroundPadding).toEqual(new Cartesian2(10, 11));
          expect(feature.backgroundEnabled).toEqual(true);
          expect(feature.scaleByDistance).toEqual(
            new NearFarScalar(1.0e4, 1.0, 1.0e6, 0.0)
          );
          expect(feature.translucencyByDistance).toEqual(
            new NearFarScalar(1.0e4, 1.0, 1.0e6, 0.0)
          );
          expect(feature.distanceDisplayCondition).toEqual(
            new DistanceDisplayCondition(0.1, 1.0e6)
          );
          expect(feature.heightOffset).toEqual(0.0);
          expect(feature.anchorLineEnabled).toEqual(true);
          expect(feature.anchorLineColor).toEqual(
            new Color(1.0, 1.0, 0.0, 1.0)
          );
          expect(feature.disableDepthTestDistance).toEqual(1.0e6);
          expect(feature.horizontalOrigin).toEqual(HorizontalOrigin.CENTER);
          expect(feature.verticalOrigin).toEqual(VerticalOrigin.CENTER);
          expect(feature.labelHorizontalOrigin).toEqual(HorizontalOrigin.RIGHT);
          expect(feature.labelVerticalOrigin).toEqual(VerticalOrigin.BOTTOM);
        }

        var position;
        for (i = 0; i < cartoPositions.length; ++i) {
          position = ellipsoid.cartographicToCartesian(cartoPositions[i]);
          scene.camera.lookAt(position, new Cartesian3(0.0, 0.0, 50.0));
          expect(scene).toRenderAndCall(function (rgba) {
            expect(rgba[0]).toBeGreaterThan(0);
            expect(rgba[1]).toBeGreaterThan(0);
            expect(rgba[2]).toEqual(0);
            expect(rgba[3]).toEqual(255);
          });
        }
      });
    });

    it("renders a point with an image", function () {
      var minHeight = 0.0;
      var maxHeight = 100.0;
      var cartoPositions = [Cartographic.fromDegrees(0.0, 0.0, 10.0)];
      var positions = encodePositions(
        rectangle,
        minHeight,
        maxHeight,
        cartoPositions
      );

      var batchTable = new Cesium3DTileBatchTable(mockTileset, 1);
      batchTable.update(mockTileset, scene.frameState);

      points = scene.primitives.add(
        new Vector3DTilePoints({
          positions: positions,
          batchTable: batchTable,
          batchIds: new Uint16Array([0]),
          rectangle: rectangle,
          minimumHeight: minHeight,
          maximumHeight: maxHeight,
        })
      );

      var style = new Cesium3DTileStyle({
        image: '"./Data/Images/Blue10x10.png"',
      });
      return loadPoints(points).then(function () {
        var features = [];
        points.createFeatures(mockTileset, features);
        points.applyStyle(style, features);

        var collection = points._billboardCollection;
        expect(collection.length).toEqual(1);
        var billboard = collection.get(0);
        expect(billboard).toBeDefined();
        expect(billboard.ready).toEqual(false);

        scene.camera.lookAt(
          Cartesian3.fromDegrees(0.0, 0.0, 10.0),
          new Cartesian3(0.0, 0.0, 50.0)
        );
        return pollToPromise(function () {
          scene.renderForSpecs();
          return billboard.ready;
        }).then(function () {
          expect(billboard.ready).toEqual(true);
          expect(scene).toRender([0, 0, 255, 255]);
        });
      });
    });

    it("renders multiple points with debug color", function () {
      var minHeight = 0.0;
      var maxHeight = 100.0;
      var cartoPositions = [
        Cartographic.fromDegrees(0.0, 0.0, 10.0),
        Cartographic.fromDegrees(5.0, 0.0, 20.0),
        Cartographic.fromDegrees(-5.0, 0.0, 1.0),
        Cartographic.fromDegrees(0.0, 6.0, 5.0),
        Cartographic.fromDegrees(0.0, -6.0, 90.0),
      ];
      var positions = encodePositions(
        rectangle,
        minHeight,
        maxHeight,
        cartoPositions
      );

      var batchTable = new Cesium3DTileBatchTable(mockTileset, 5);
      batchTable.update(mockTileset, scene.frameState);

      points = scene.primitives.add(
        new Vector3DTilePoints({
          positions: positions,
          batchTable: batchTable,
          batchIds: new Uint16Array([0, 1, 2, 3, 4]),
          rectangle: rectangle,
          minimumHeight: minHeight,
          maximumHeight: maxHeight,
        })
      );
      var style = new Cesium3DTileStyle({
        verticalOrigin: VerticalOrigin.BOTTOM,
      });
      return loadPoints(points).then(function () {
        var features = [];
        points.createFeatures(mockTileset, features);
        points.applyStyle(style, features);
        points.applyDebugSettings(true, Color.YELLOW);

        var i;
        var position;
        for (i = 0; i < cartoPositions.length; ++i) {
          position = ellipsoid.cartographicToCartesian(cartoPositions[i]);
          scene.camera.lookAt(position, new Cartesian3(0.0, 0.0, 50.0));
          expect(scene).toRenderAndCall(function (rgba) {
            expect(rgba[0]).toBeGreaterThan(0);
            expect(rgba[1]).toBeGreaterThan(0);
            expect(rgba[2]).toEqual(0);
            expect(rgba[3]).toEqual(255);
          });
        }

        points.applyDebugSettings(false);

        for (i = 0; i < cartoPositions.length; ++i) {
          position = ellipsoid.cartographicToCartesian(cartoPositions[i]);
          scene.camera.lookAt(position, new Cartesian3(0.0, 0.0, 50.0));
          expect(scene).toRenderAndCall(function (rgba) {
            expect(rgba[0]).toBeGreaterThan(0);
            expect(rgba[0]).toEqual(rgba[1]);
            expect(rgba[0]).toEqual(rgba[2]);
            expect(rgba[3]).toEqual(255);
          });
        }
      });
    });

    it("isDestroyed", function () {
      points = new Vector3DTilePoints({});
      expect(points.isDestroyed()).toEqual(false);
      points.destroy();
      expect(points.isDestroyed()).toEqual(true);
    });
  },
  "WebGL"
);
