import { Cartesian2 } from "../../Source/Cesium.js";
import { Cartesian3 } from "../../Source/Cesium.js";
import { Ellipsoid } from "../../Source/Cesium.js";
import { Event } from "../../Source/Cesium.js";
import { JulianDate } from "../../Source/Cesium.js";
import { CustomDataSource } from "../../Source/Cesium.js";
import { DataSourceDisplay } from "../../Source/Cesium.js";
import { Entity } from "../../Source/Cesium.js";
import { EntityCluster } from "../../Source/Cesium.js";
import { SceneTransforms } from "../../Source/Cesium.js";
import createCanvas from "../createCanvas.js";
import createScene from "../createScene.js";

describe(
  "DataSources/EntityCluster",
  function () {
    var scene;
    var cluster;
    var depth;
    var farDepth;

    beforeAll(function () {
      scene = createScene({
        canvas: createCanvas(10, 10),
      });
      scene.globe = {
        ellipsoid: Ellipsoid.WGS84,
        _surface: {
          tileProvider: {
            ready: true,
          },
          _tileLoadQueueHigh: [],
          _tileLoadQueueMedium: [],
          _tileLoadQueueLow: [],
          _debug: {
            tilesWaitingForChildren: 0,
          },
        },
        terrainProviderChanged: new Event(),
        imageryLayersUpdatedEvent: new Event(),
        beginFrame: function () {},
        update: function () {},
        render: function () {},
        endFrame: function () {},
      };

      scene.globe.getHeight = function () {
        return 0.0;
      };

      scene.globe.destroy = function () {};

      scene.globe._surface.updateHeight = function () {};

      scene.globe.terrainProviderChanged = new Event();
      Object.defineProperties(scene.globe, {
        terrainProvider: {
          set: function (value) {
            this.terrainProviderChanged.raiseEvent(value);
          },
        },
      });

      var camera = scene.camera;
      camera.setView({
        destination: Cartesian3.fromDegrees(0.0, 0.0, 10000.0),
      });

      scene.initializeFrame();
      scene.render();

      if (scene.logarithmicDepthBuffer) {
        depth = farDepth = 0.1;
      } else {
        depth = 0.5;
        farDepth = 0.9;
      }
    });

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

    afterEach(function () {
      cluster = cluster && cluster.destroy();
    });

    it("constructor sets default properties", function () {
      cluster = new EntityCluster();
      expect(cluster.enabled).toEqual(false);
      expect(cluster.show).toEqual(true);
      expect(cluster.pixelRange).toEqual(80);
      expect(cluster.minimumClusterSize).toEqual(2);
      expect(cluster.clusterBillboards).toEqual(true);
      expect(cluster.clusterLabels).toEqual(true);
      expect(cluster.clusterPoints).toEqual(true);

      cluster.enabled = true;
      expect(cluster.enabled).toEqual(true);

      cluster.pixelRange = 30;
      expect(cluster.pixelRange).toEqual(30);

      cluster.minimumClusterSize = 5;
      expect(cluster.minimumClusterSize).toEqual(5);
    });

    it("constructor sets expected properties", function () {
      var options = {
        enabled: true,
        show: false,
        pixelRange: 30,
        minimumClusterSize: 5,
        clusterBillboards: false,
        clusterLabels: false,
        clusterPoints: false,
      };
      cluster = new EntityCluster(options);
      expect(cluster.enabled).toEqual(options.enabled);
      expect(cluster.show).toEqual(false);
      expect(cluster.pixelRange).toEqual(options.pixelRange);
      expect(cluster.minimumClusterSize).toEqual(options.minimumClusterSize);
      expect(cluster.clusterBillboards).toEqual(options.clusterBillboards);
      expect(cluster.clusterLabels).toEqual(options.clusterLabels);
      expect(cluster.clusterPoints).toEqual(options.clusterPoints);
    });

    function createBillboardImage() {
      var canvas = document.createElement("canvas");
      canvas.height = canvas.width = 1;

      var context2D = canvas.getContext("2d");
      context2D.clearRect(0, 0, length, length);
      context2D.fillStyle = "#FF0000";
      context2D.fillRect(0, 0, length, length);

      return canvas;
    }

    it("clusters billboards", function () {
      cluster = new EntityCluster();
      cluster._initialize(scene);

      var entity = new Entity();
      var billboard = cluster.getBillboard(entity);
      billboard.id = entity;
      billboard.image = createBillboardImage();
      billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(
        scene,
        new Cartesian2(0.0, 0.0),
        depth
      );

      entity = new Entity();
      billboard = cluster.getBillboard(entity);
      billboard.id = entity;
      billboard.image = createBillboardImage();
      billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(
        scene,
        new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight),
        depth
      );

      var frameState = scene.frameState;
      cluster.update(frameState);

      expect(cluster._clusterLabelCollection).not.toBeDefined();

      cluster.enabled = true;
      cluster.update(frameState);

      expect(cluster._clusterLabelCollection).toBeDefined();
      expect(cluster._clusterLabelCollection.length).toEqual(1);

      cluster.clusterBillboards = false;
      cluster.update(frameState);

      expect(cluster._clusterLabelCollection).not.toBeDefined();
    });

    it("clusters billboards on first update", function () {
      cluster = new EntityCluster();
      cluster._initialize(scene);

      var entity = new Entity();
      var billboard = cluster.getBillboard(entity);
      billboard.id = entity;
      billboard.image = createBillboardImage();
      billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(
        scene,
        new Cartesian2(0.0, 0.0),
        depth
      );

      entity = new Entity();
      billboard = cluster.getBillboard(entity);
      billboard.id = entity;
      billboard.image = createBillboardImage();
      billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(
        scene,
        new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight),
        depth
      );

      cluster.enabled = true;
      cluster.update(scene.frameState);

      expect(cluster._clusterLabelCollection).toBeDefined();
      expect(cluster._clusterLabelCollection.length).toEqual(1);
    });

    it("clusters labels", function () {
      cluster = new EntityCluster();
      cluster._initialize(scene);

      var entity = new Entity();
      var label = cluster.getLabel(entity);
      label.id = entity;
      label.text = "a";
      label.position = SceneTransforms.drawingBufferToWgs84Coordinates(
        scene,
        new Cartesian2(0.0, 0.0),
        depth
      );

      entity = new Entity();
      label = cluster.getLabel(entity);
      label.id = entity;
      label.text = "b";
      label.position = SceneTransforms.drawingBufferToWgs84Coordinates(
        scene,
        new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight),
        depth
      );

      var frameState = scene.frameState;
      cluster.update(frameState);

      expect(cluster._clusterLabelCollection).not.toBeDefined();

      cluster.enabled = true;
      cluster.update(frameState);

      expect(cluster._clusterLabelCollection).toBeDefined();
      expect(cluster._clusterLabelCollection.length).toEqual(1);

      cluster.clusterLabels = false;
      cluster.update(frameState);

      expect(cluster._clusterLabelCollection).not.toBeDefined();
    });

    it("clusters labels on first update", function () {
      cluster = new EntityCluster();
      cluster._initialize(scene);

      var entity = new Entity();
      var label = cluster.getLabel(entity);
      label.id = entity;
      label.text = "a";
      label.position = SceneTransforms.drawingBufferToWgs84Coordinates(
        scene,
        new Cartesian2(0.0, 0.0),
        depth
      );

      entity = new Entity();
      label = cluster.getLabel(entity);
      label.id = entity;
      label.text = "b";
      label.position = SceneTransforms.drawingBufferToWgs84Coordinates(
        scene,
        new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight),
        depth
      );

      cluster.enabled = true;
      cluster.update(scene.frameState);

      expect(cluster._clusterLabelCollection).toBeDefined();
      expect(cluster._clusterLabelCollection.length).toEqual(1);
    });

    it("clusters points", function () {
      cluster = new EntityCluster();
      cluster._initialize(scene);

      var entity = new Entity();
      var point = cluster.getPoint(entity);
      point.id = entity;
      point.pixelSize = 1;
      point.position = SceneTransforms.drawingBufferToWgs84Coordinates(
        scene,
        new Cartesian2(0.0, 0.0),
        depth
      );

      entity = new Entity();
      point = cluster.getPoint(entity);
      point.id = entity;
      point.pixelSize = 1;
      point.position = SceneTransforms.drawingBufferToWgs84Coordinates(
        scene,
        new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight),
        depth
      );

      var frameState = scene.frameState;
      cluster.update(frameState);

      expect(cluster._clusterLabelCollection).not.toBeDefined();

      cluster.enabled = true;
      cluster.update(frameState);

      expect(cluster._clusterLabelCollection).toBeDefined();
      expect(cluster._clusterLabelCollection.length).toEqual(1);

      cluster.clusterPoints = false;
      cluster.update(frameState);

      expect(cluster._clusterLabelCollection).not.toBeDefined();
    });

    it("clusters points on first update", function () {
      cluster = new EntityCluster();
      cluster._initialize(scene);

      var entity = new Entity();
      var point = cluster.getPoint(entity);
      point.id = entity;
      point.pixelSize = 1;
      point.position = SceneTransforms.drawingBufferToWgs84Coordinates(
        scene,
        new Cartesian2(0.0, 0.0),
        depth
      );

      entity = new Entity();
      point = cluster.getPoint(entity);
      point.id = entity;
      point.pixelSize = 1;
      point.position = SceneTransforms.drawingBufferToWgs84Coordinates(
        scene,
        new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight),
        depth
      );

      cluster.enabled = true;
      cluster.update(scene.frameState);

      expect(cluster._clusterLabelCollection).toBeDefined();
      expect(cluster._clusterLabelCollection.length).toEqual(1);
    });

    it("clusters points that have labels", function () {
      cluster = new EntityCluster();
      cluster._initialize(scene);

      var entity = new Entity();
      var point = cluster.getPoint(entity);
      point.id = entity;
      point.pixelSize = 1;
      point.position = SceneTransforms.drawingBufferToWgs84Coordinates(
        scene,
        new Cartesian2(0.0, 0.0),
        depth
      );

      entity = new Entity();
      point = cluster.getPoint(entity);
      point.id = entity;
      point.pixelSize = 1;
      point.position = SceneTransforms.drawingBufferToWgs84Coordinates(
        scene,
        new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight),
        depth
      );

      var frameState = scene.frameState;
      cluster.update(frameState);

      expect(cluster._clusterLabelCollection).not.toBeDefined();

      point.id.label = cluster.getLabel(entity);

      cluster.enabled = true;
      cluster.update(frameState);

      expect(cluster._clusterLabelCollection).toBeDefined();
      expect(cluster._clusterLabelCollection.length).toEqual(1);

      cluster.clusterPoints = false;
      cluster.update(frameState);

      expect(cluster._clusterLabelCollection).not.toBeDefined();
    });

    it("records entity collection indices on getting billboard, label and point", function () {
      cluster = new EntityCluster();
      cluster._initialize(scene);

      var entity = new Entity();
      cluster.getBillboard(entity);
      cluster.getLabel(entity);
      cluster.getPoint(entity);

      expect(
        cluster._collectionIndicesByEntity[entity.id].billboardIndex
      ).toBeDefined();
      expect(
        cluster._collectionIndicesByEntity[entity.id].labelIndex
      ).toBeDefined();
      expect(
        cluster._collectionIndicesByEntity[entity.id].pointIndex
      ).toBeDefined();
    });

    it("removes entity collection indices when billboard, label and point have been removed", function () {
      cluster = new EntityCluster();
      cluster._initialize(scene);

      var entity = new Entity();
      cluster.getBillboard(entity);
      cluster.getLabel(entity);
      cluster.getPoint(entity);

      cluster.removeBillboard(entity);
      cluster.removeLabel(entity);
      cluster.removePoint(entity);

      expect(cluster._collectionIndicesByEntity[entity.id]).toBeUndefined();
    });

    it("can destroy cluster and re-add entities", function () {
      cluster = new EntityCluster();
      cluster._initialize(scene);

      var entity1 = new Entity();
      var billboard = cluster.getBillboard(entity1);
      billboard.id = entity1;

      var entity2 = new Entity();
      var label = cluster.getLabel(entity2);
      label.id = entity2;

      var entity3 = new Entity();
      var point = cluster.getPoint(entity3);
      point.id = entity3;

      cluster.destroy();
      expect(cluster._billboardCollection).not.toBeDefined();
      expect(cluster._labelCollection).not.toBeDefined();
      expect(cluster._pointCollection).not.toBeDefined();

      expect(cluster.getBillboard(entity1)).toBeDefined();
      expect(cluster.getLabel(entity2)).toBeDefined();
      expect(cluster.getPoint(entity3)).toBeDefined();
      expect(cluster._billboardCollection).toBeDefined();
      expect(cluster._labelCollection).toBeDefined();
      expect(cluster._pointCollection).toBeDefined();
    });

    it("does not remove entity collection indices when at least one of billboard, label and point remain", function () {
      cluster = new EntityCluster();
      cluster._initialize(scene);

      var entity = new Entity();
      cluster.getBillboard(entity);
      cluster.getLabel(entity);
      cluster.getPoint(entity);

      cluster.removeBillboard(entity);
      cluster.removeLabel(entity);

      expect(cluster._collectionIndicesByEntity[entity.id]).toBeDefined();
    });

    it("pixel range", function () {
      cluster = new EntityCluster();
      cluster._initialize(scene);

      var entity = new Entity();
      var billboard = cluster.getBillboard(entity);
      billboard.id = entity;
      billboard.image = createBillboardImage();
      billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(
        scene,
        new Cartesian2(0.0, 0.0),
        depth
      );

      entity = new Entity();
      billboard = cluster.getBillboard(entity);
      billboard.id = entity;
      billboard.image = createBillboardImage();
      billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(
        scene,
        new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight),
        depth
      );

      var frameState = scene.frameState;
      cluster.update(frameState);

      expect(cluster._clusterLabelCollection).not.toBeDefined();

      cluster.enabled = true;
      cluster.update(frameState);

      expect(cluster._clusterLabelCollection).toBeDefined();
      expect(cluster._clusterLabelCollection.length).toEqual(1);

      cluster.pixelRange = 1;
      cluster.update(frameState);

      expect(cluster._clusterLabelCollection).not.toBeDefined();
    });

    it("minimum cluster size", function () {
      cluster = new EntityCluster();
      cluster._initialize(scene);

      var entity = new Entity();
      var billboard = cluster.getBillboard(entity);
      billboard.id = entity;
      billboard.image = createBillboardImage();
      billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(
        scene,
        new Cartesian2(0.0, 0.0),
        depth
      );

      entity = new Entity();
      billboard = cluster.getBillboard(entity);
      billboard.id = entity;
      billboard.image = createBillboardImage();
      billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(
        scene,
        new Cartesian2(scene.canvas.clientWidth, 0),
        depth
      );

      entity = new Entity();
      billboard = cluster.getBillboard(entity);
      billboard.id = entity;
      billboard.image = createBillboardImage();
      billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(
        scene,
        new Cartesian2(0, scene.canvas.clientHeight),
        depth
      );

      entity = new Entity();
      billboard = cluster.getBillboard(entity);
      billboard.id = entity;
      billboard.image = createBillboardImage();
      billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(
        scene,
        new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight),
        depth
      );

      var frameState = scene.frameState;
      cluster.update(frameState);

      expect(cluster._clusterLabelCollection).not.toBeDefined();

      cluster.enabled = true;
      cluster.update(frameState);

      expect(cluster._clusterLabelCollection).toBeDefined();
      expect(cluster._clusterLabelCollection.length).toEqual(1);

      cluster.minimumClusterSize = 5;
      cluster.update(frameState);

      expect(cluster._clusterLabelCollection).not.toBeDefined();
    });

    it("clusters around the same point", function () {
      cluster = new EntityCluster();
      cluster._initialize(scene);

      var entity = new Entity();
      var billboard = cluster.getBillboard(entity);
      billboard.id = entity;
      billboard.image = createBillboardImage();
      billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(
        scene,
        new Cartesian2(0.0, 0.0),
        farDepth
      );

      entity = new Entity();
      billboard = cluster.getBillboard(entity);
      billboard.id = entity;
      billboard.image = createBillboardImage();
      billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(
        scene,
        new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight),
        farDepth
      );

      var frameState = scene.frameState;
      cluster.update(frameState);

      expect(cluster._clusterLabelCollection).not.toBeDefined();

      cluster.enabled = true;
      cluster.update(frameState);

      expect(cluster._clusterLabelCollection).toBeDefined();
      expect(cluster._clusterLabelCollection.length).toEqual(1);

      var position = Cartesian3.clone(
        cluster._clusterLabelCollection.get(0).position
      );

      scene.camera.moveForward(1.0e-6);
      cluster.pixelRange = cluster.pixelRange - 1;
      cluster.update(frameState);

      expect(cluster._clusterLabelCollection).toBeDefined();
      expect(cluster._clusterLabelCollection.length).toEqual(1);
      expect(cluster._clusterLabelCollection.get(0).position).toEqual(position);
    });

    it("custom cluster styling", function () {
      cluster = new EntityCluster();
      cluster._initialize(scene);

      cluster.clusterEvent.addEventListener(function (
        clusteredEntities,
        cluster
      ) {
        cluster.billboard.show = true;
        cluster.billboard.image = createBillboardImage();
        cluster.label.text = "cluster";
      });

      var entity = new Entity();
      var point = cluster.getPoint(entity);
      point.id = entity;
      point.pixelSize = 1;
      point.position = SceneTransforms.drawingBufferToWgs84Coordinates(
        scene,
        new Cartesian2(0.0, 0.0),
        farDepth
      );

      entity = new Entity();
      point = cluster.getPoint(entity);
      point.id = entity;
      point.pixelSize = 1;
      point.position = SceneTransforms.drawingBufferToWgs84Coordinates(
        scene,
        new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight),
        farDepth
      );

      var frameState = scene.frameState;
      cluster.update(frameState);

      expect(cluster._clusterBillboardCollection).not.toBeDefined();
      expect(cluster._clusterLabelCollection).not.toBeDefined();

      cluster.enabled = true;
      cluster.update(frameState);

      expect(cluster._clusterLabelCollection).toBeDefined();
      expect(cluster._clusterLabelCollection.length).toEqual(1);
      expect(cluster._clusterLabelCollection.get(0).text).toEqual("cluster");

      expect(cluster._clusterBillboardCollection).toBeDefined();
      expect(cluster._clusterBillboardCollection.length).toEqual(1);
    });

    it("renders billboards with invisible labels that are not clustered", function () {
      cluster = new EntityCluster();
      cluster._initialize(scene);
      cluster.minimumClusterSize = 3;

      var dataSource = new CustomDataSource("test");
      dataSource.clustering = cluster;
      dataSource._visualizers = DataSourceDisplay.defaultVisualizersCallback(
        scene,
        cluster,
        dataSource
      );

      var entityCollection = dataSource.entities;

      entityCollection.add({
        position: SceneTransforms.drawingBufferToWgs84Coordinates(
          scene,
          new Cartesian2(0.0, 0.0),
          depth
        ),
        billboard: {
          image: createBillboardImage(),
        },
        label: {
          show: true,
        },
      });

      entityCollection.add({
        position: SceneTransforms.drawingBufferToWgs84Coordinates(
          scene,
          new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight),
          depth
        ),
        billboard: {
          image: createBillboardImage(),
        },
        label: {
          show: true,
        },
      });

      var visualizers = dataSource._visualizers;
      var length = visualizers.length;
      for (var i = 0; i < length; i++) {
        visualizers[i].update(JulianDate.now());
      }

      var frameState = scene.frameState;
      cluster.update(frameState);

      expect(cluster._clusterBillboardCollection).not.toBeDefined();
      expect(cluster._clusterLabelCollection).not.toBeDefined();

      cluster.enabled = true;
      cluster.update(frameState);

      expect(cluster._clusterLabelCollection).not.toBeDefined();
      expect(cluster._clusterBillboardCollection).not.toBeDefined();

      expect(cluster._labelCollection).not.toBeDefined();
      expect(cluster._billboardCollection).toBeDefined();
      expect(cluster._billboardCollection.length).toEqual(2);
    });
  },
  "WebGL"
);
