import { BoundingSphere } from "../../Source/Cesium.js";
import { Cartesian3 } from "../../Source/Cesium.js";
import { Cartographic } from "../../Source/Cesium.js";
import { Ellipsoid } from "../../Source/Cesium.js";
import { Math as CesiumMath } from "../../Source/Cesium.js";
import { Rectangle } from "../../Source/Cesium.js";
import { Cesium3DTileBatchTable } from "../../Source/Cesium.js";
import { ColorBlendMode } from "../../Source/Cesium.js";
import { Vector3DTilePolylines } from "../../Source/Cesium.js";
import createScene from "../createScene.js";
import pollToPromise from "../pollToPromise.js";

describe(
  "Scene/Vector3DTilePolylines",
  function () {
    var scene;
    var rectangle;
    var polylines;

    var ellipsoid = Ellipsoid.WGS84;

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

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

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

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

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

    function loadPolylines(polylines) {
      var ready = false;
      polylines.readyPromise.then(function () {
        ready = true;
      });
      return pollToPromise(function () {
        polylines.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 polyline", function () {
      var minHeight = 0.0;
      var maxHeight = 5.0;
      var cartoPositions = [
        Cartographic.fromDegrees(0.0, 0.0, 1.0),
        Cartographic.fromDegrees(1.0, 0.0, 2.0),
      ];
      var positions = encodePositions(
        rectangle,
        minHeight,
        maxHeight,
        cartoPositions
      );

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

      var center = ellipsoid.cartographicToCartesian(
        Rectangle.center(rectangle)
      );

      polylines = scene.primitives.add(
        new Vector3DTilePolylines({
          positions: positions,
          widths: new Uint16Array([10]),
          counts: new Uint32Array([2]),
          batchIds: new Uint16Array([0]),
          rectangle: rectangle,
          minimumHeight: minHeight,
          maximumHeight: maxHeight,
          center: center,
          boundingVolume: new BoundingSphere(center, 1000000.0),
          batchTable: batchTable,
        })
      );
      return loadPolylines(polylines).then(function () {
        scene.camera.lookAt(
          Cartesian3.fromDegrees(0.5, 0.0, 1.5),
          new Cartesian3(0.0, 0.0, 1.0)
        );
        expect(scene).toRender([255, 255, 255, 255]);
      });
    });

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

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

      var center = ellipsoid.cartographicToCartesian(
        Rectangle.center(rectangle)
      );

      polylines = scene.primitives.add(
        new Vector3DTilePolylines({
          positions: positions,
          widths: new Uint16Array([10, 10, 10]),
          counts: new Uint32Array([2, 2, 4]),
          batchIds: new Uint16Array([0, 1, 2]),
          rectangle: rectangle,
          minimumHeight: minHeight,
          maximumHeight: maxHeight,
          center: center,
          boundingVolume: new BoundingSphere(center, 1000000.0),
          batchTable: batchTable,
        })
      );
      return loadPolylines(polylines).then(function () {
        for (var i = 0; i < cartoPositions.length; i += 2) {
          var p1 = cartoPositions[i];
          var p2 = cartoPositions[i + 1];

          var longitude = CesiumMath.lerp(p1.longitude, p2.longitude, 0.5);
          var latitude = CesiumMath.lerp(p1.latitude, p2.latitude, 0.5);
          var height = CesiumMath.lerp(p1.height, p2.height, 0.5);
          var target = Cartesian3.fromRadians(longitude, latitude, height);
          scene.camera.lookAt(target, new Cartesian3(0.0, 0.0, 1.0));
          expect(scene).toRender([255, 255, 255, 255]);
        }
      });
    });

    it("picks a polyline", function () {
      var minHeight = 0.0;
      var maxHeight = 5.0;
      var cartoPositions = [
        Cartographic.fromDegrees(0.0, 0.0, 1.0),
        Cartographic.fromDegrees(1.0, 0.0, 2.0),
      ];
      var positions = encodePositions(
        rectangle,
        minHeight,
        maxHeight,
        cartoPositions
      );

      var batchTable = new Cesium3DTileBatchTable(mockTileset, 1);

      var center = ellipsoid.cartographicToCartesian(
        Rectangle.center(rectangle)
      );

      polylines = scene.primitives.add(
        new Vector3DTilePolylines({
          positions: positions,
          widths: new Uint16Array([10]),
          counts: new Uint32Array([2]),
          batchIds: new Uint16Array([0]),
          rectangle: rectangle,
          minimumHeight: minHeight,
          maximumHeight: maxHeight,
          center: center,
          boundingVolume: new BoundingSphere(center, 1000000.0),
          batchTable: batchTable,
        })
      );
      return loadPolylines(polylines).then(function () {
        scene.camera.lookAt(
          Cartesian3.fromDegrees(0.5, 0.0, 1.5),
          new Cartesian3(0.0, 0.0, 1.0)
        );

        var features = [];
        polylines.createFeatures(mockTileset, 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]);
        });

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

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