import { ApproximateTerrainHeights } from "../../Source/Cesium.js";
import { BoundingSphere } from "../../Source/Cesium.js";
import { Cartesian3 } from "../../Source/Cesium.js";
import { Iso8601 } from "../../Source/Cesium.js";
import { BoundingSphereState } from "../../Source/Cesium.js";
import { DataSourceCollection } from "../../Source/Cesium.js";
import { DataSourceDisplay } from "../../Source/Cesium.js";
import { Entity } from "../../Source/Cesium.js";
import { GroundPolylinePrimitive } from "../../Source/Cesium.js";
import { GroundPrimitive } from "../../Source/Cesium.js";
import createScene from "../createScene.js";
import MockDataSource from "../MockDataSource.js";
import { when } from "../../Source/Cesium.js";

describe(
  "DataSources/DataSourceDisplay",
  function () {
    var dataSourceCollection;
    var scene;
    var display;
    beforeAll(function () {
      scene = createScene();
      dataSourceCollection = new DataSourceCollection();

      return when.join(
        GroundPrimitive.initializeTerrainHeights(),
        GroundPolylinePrimitive.initializeTerrainHeights()
      );
    });

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

      // Leave ground primitive uninitialized
      ApproximateTerrainHeights._initPromise = undefined;
      ApproximateTerrainHeights._terrainHeights = undefined;
    });

    afterEach(function () {
      if (!display.isDestroyed()) {
        display.destroy();
      }
      dataSourceCollection.removeAll();
    });

    function MockVisualizer() {
      this.updatesCalled = 0;
      this.lastUpdateTime = undefined;
      this.destroyed = false;

      this.getBoundingSphereResult = undefined;
      this.getBoundingSphereState = undefined;
    }

    MockVisualizer.prototype.update = function (time) {
      this.lastUpdateTime = time;
      this.updatesCalled++;
      return true;
    };

    MockVisualizer.prototype.getBoundingSphere = function (entity, result) {
      this.getBoundingSphereResult.clone(result);
      return this.getBoundingSphereState;
    };

    MockVisualizer.prototype.isDestroyed = function () {
      return this.destroyed;
    };

    MockVisualizer.prototype.destroy = function () {
      this.destroyed = true;
    };

    function visualizersCallback() {
      return [new MockVisualizer()];
    }

    it("constructor sets expected values", function () {
      display = new DataSourceDisplay({
        scene: scene,
        dataSourceCollection: dataSourceCollection,
        visualizersCallback: visualizersCallback,
      });

      expect(display.scene).toBe(scene);
      expect(display.dataSources).toBe(dataSourceCollection);
      expect(display.isDestroyed()).toEqual(false);
      expect(display.defaultDataSource).toBeDefined();

      display.destroy();
    });

    it("Computes complete bounding sphere.", function () {
      var visualizer1 = new MockVisualizer();
      visualizer1.getBoundingSphereResult = new BoundingSphere(
        new Cartesian3(1, 2, 3),
        456
      );
      visualizer1.getBoundingSphereState = BoundingSphereState.DONE;

      var visualizer2 = new MockVisualizer();
      visualizer2.getBoundingSphereResult = new BoundingSphere(
        new Cartesian3(7, 8, 9),
        1011
      );
      visualizer2.getBoundingSphereState = BoundingSphereState.DONE;

      display = new DataSourceDisplay({
        scene: scene,
        dataSourceCollection: dataSourceCollection,
        visualizersCallback: function () {
          return [visualizer1, visualizer2];
        },
      });

      var entity = new Entity();
      var dataSource = new MockDataSource();
      dataSource.entities.add(entity);
      display.dataSources.add(dataSource);

      display.update(Iso8601.MINIMUM_VALUE);

      var result = new BoundingSphere();
      var state = display.getBoundingSphere(entity, true, result);

      var expected = BoundingSphere.union(
        visualizer1.getBoundingSphereResult,
        visualizer2.getBoundingSphereResult
      );

      expect(state).toBe(BoundingSphereState.DONE);
      expect(result).toEqual(expected);
    });

    it("Computes partial bounding sphere.", function () {
      var visualizer1 = new MockVisualizer();
      visualizer1.getBoundingSphereResult = new BoundingSphere(
        new Cartesian3(1, 2, 3),
        456
      );
      visualizer1.getBoundingSphereState = BoundingSphereState.PENDING;

      var visualizer2 = new MockVisualizer();
      visualizer2.getBoundingSphereResult = new BoundingSphere(
        new Cartesian3(7, 8, 9),
        1011
      );
      visualizer2.getBoundingSphereState = BoundingSphereState.DONE;

      display = new DataSourceDisplay({
        scene: scene,
        dataSourceCollection: dataSourceCollection,
        visualizersCallback: function () {
          return [visualizer1, visualizer2];
        },
      });

      var entity = new Entity();
      var dataSource = new MockDataSource();
      dataSource.entities.add(entity);
      display.dataSources.add(dataSource);

      display.update(Iso8601.MINIMUM_VALUE);

      var result = new BoundingSphere();
      var state = display.getBoundingSphere(entity, true, result);

      expect(state).toBe(BoundingSphereState.DONE);
      expect(result).toEqual(visualizer2.getBoundingSphereResult);
    });

    it("Fails complete bounding sphere if allowPartial false.", function () {
      var visualizer1 = new MockVisualizer();
      visualizer1.getBoundingSphereResult = new BoundingSphere(
        new Cartesian3(1, 2, 3),
        456
      );
      visualizer1.getBoundingSphereState = BoundingSphereState.PENDING;

      var visualizer2 = new MockVisualizer();
      visualizer2.getBoundingSphereResult = new BoundingSphere(
        new Cartesian3(7, 8, 9),
        1011
      );
      visualizer2.getBoundingSphereState = BoundingSphereState.DONE;

      display = new DataSourceDisplay({
        scene: scene,
        dataSourceCollection: dataSourceCollection,
        visualizersCallback: function () {
          return [visualizer1, visualizer2];
        },
      });

      var entity = new Entity();
      display.defaultDataSource.entities.add(entity);

      var result = new BoundingSphere();
      var state = display.getBoundingSphere(entity, false, result);

      expect(state).toBe(BoundingSphereState.PENDING);
    });

    it("Fails bounding sphere for entity without visualization.", function () {
      display = new DataSourceDisplay({
        dataSourceCollection: dataSourceCollection,
        scene: scene,
      });
      var entity = new Entity();
      var dataSource = new MockDataSource();
      dataSource.entities.add(entity);
      display.dataSources.add(dataSource);
      display.update(Iso8601.MINIMUM_VALUE);

      var result = new BoundingSphere();
      var state = display.getBoundingSphere(entity, false, result);
      expect(state).toBe(BoundingSphereState.FAILED);
      display.destroy();
    });

    it("Fails bounding sphere for entity not in a data source.", function () {
      display = new DataSourceDisplay({
        dataSourceCollection: dataSourceCollection,
        scene: scene,
      });
      display.update(Iso8601.MINIMUM_VALUE);

      var entity = new Entity();
      var result = new BoundingSphere();
      var state = display.getBoundingSphere(entity, false, result);
      expect(state).toBe(BoundingSphereState.FAILED);
      display.destroy();
    });

    it("Compute bounding sphere throws without entity.", function () {
      display = new DataSourceDisplay({
        dataSourceCollection: dataSourceCollection,
        scene: scene,
      });
      var result = new BoundingSphere();
      expect(function () {
        display.getBoundingSphere(undefined, false, result);
      }).toThrowDeveloperError();
    });

    it("Compute bounding sphere throws without result.", function () {
      display = new DataSourceDisplay({
        dataSourceCollection: dataSourceCollection,
        scene: scene,
      });
      var entity = new Entity();
      expect(function () {
        display.getBoundingSphere(entity, false, undefined);
      }).toThrowDeveloperError();
    });

    it("Compute bounding sphere throws without allowPartial.", function () {
      display = new DataSourceDisplay({
        dataSourceCollection: dataSourceCollection,
        scene: scene,
      });
      var entity = new Entity();
      var result = new BoundingSphere();
      expect(function () {
        display.getBoundingSphere(entity, undefined, result);
      }).toThrowDeveloperError();
    });

    it("destroy does not destroy underlying data sources", function () {
      var dataSource = new MockDataSource();
      dataSourceCollection.add(dataSource);

      display = new DataSourceDisplay({
        scene: scene,
        dataSourceCollection: dataSourceCollection,
      });

      expect(dataSource.destroyed).toEqual(false);

      display.destroy();

      expect(dataSource.destroyed).toEqual(false);
      expect(display.isDestroyed()).toEqual(true);
    });

    it("calling update updates data sources", function () {
      var source1 = new MockDataSource();
      var source2 = new MockDataSource();

      display = new DataSourceDisplay({
        scene: scene,
        dataSourceCollection: dataSourceCollection,
        visualizersCallback: visualizersCallback,
      });
      dataSourceCollection.add(source1);
      dataSourceCollection.add(source2);

      var source1Visualizer = source1._visualizers[0];
      expect(source1Visualizer).toBeInstanceOf(MockVisualizer);

      var source2Visualizer = source2._visualizers[0];
      expect(source2Visualizer).toBeInstanceOf(MockVisualizer);

      //Nothing should have happened yet because we haven't called update.
      expect(source1Visualizer.updatesCalled).toEqual(0);
      expect(source2Visualizer.updatesCalled).toEqual(0);

      //Update should call update on the visualizers
      display.update(Iso8601.MINIMUM_VALUE);
      expect(source1Visualizer.lastUpdateTime).toEqual(Iso8601.MINIMUM_VALUE);
      expect(source1Visualizer.updatesCalled).toEqual(1);
      expect(source2Visualizer.lastUpdateTime).toEqual(Iso8601.MINIMUM_VALUE);
      expect(source2Visualizer.updatesCalled).toEqual(1);
    });

    it("ready is true when datasources are ready", function () {
      var source1 = new MockDataSource();
      var source2 = new MockDataSource();

      display = new DataSourceDisplay({
        scene: scene,
        dataSourceCollection: dataSourceCollection,
        visualizersCallback: visualizersCallback,
      });
      expect(display.ready).toBe(false);

      dataSourceCollection.add(source1);
      dataSourceCollection.add(source2);
      display.update(Iso8601.MINIMUM_VALUE);
      expect(display.ready).toBe(true);

      spyOn(MockVisualizer.prototype, "update").and.returnValue(false);
      display.update(Iso8601.MINIMUM_VALUE);
      expect(display.ready).toBe(false);
    });

    it("constructor throws if scene undefined", function () {
      expect(function () {
        return new DataSourceDisplay({
          scene: undefined,
          dataSourceCollection: dataSourceCollection,
          visualizersCallback: visualizersCallback,
        });
      }).toThrowDeveloperError();
    });

    it("constructor throws if options undefined", function () {
      expect(function () {
        return new DataSourceDisplay(undefined);
      }).toThrowDeveloperError();
    });

    it("constructor throws if dataSourceCollection undefined", function () {
      expect(function () {
        return new DataSourceDisplay({
          scene: scene,
          dataSourceCollection: undefined,
          visualizersCallback: visualizersCallback,
        });
      }).toThrowDeveloperError();
    });

    it("update throws if time undefined", function () {
      display = new DataSourceDisplay({
        scene: scene,
        dataSourceCollection: dataSourceCollection,
        visualizersCallback: visualizersCallback,
      });
      expect(function () {
        return display.update();
      }).toThrowDeveloperError();
    });

    it("verify update returns false till terrain heights are initialized", function () {
      ApproximateTerrainHeights._initPromise = undefined;
      ApproximateTerrainHeights._terrainHeights = undefined;

      var source1 = new MockDataSource();
      var source2 = new MockDataSource();

      display = new DataSourceDisplay({
        scene: scene,
        dataSourceCollection: dataSourceCollection,
        visualizersCallback: visualizersCallback,
      });
      dataSourceCollection.add(source1);
      dataSourceCollection.add(source2);
      display.update(Iso8601.MINIMUM_VALUE);
      expect(display.ready).toBe(false);

      return GroundPrimitive.initializeTerrainHeights().then(function () {
        display.update(Iso8601.MINIMUM_VALUE);
        expect(display.ready).toBe(true);
      });
    });

    it("sets dataSource primitives on add", function () {
      var source = new MockDataSource();

      display = new DataSourceDisplay({
        scene: scene,
        dataSourceCollection: dataSourceCollection,
        visualizersCallback: visualizersCallback,
      });

      dataSourceCollection.add(source);

      expect(source._primitives).toBeDefined();
      expect(source._groundPrimitives).toBeDefined();

      expect(display._primitives.contains(source._primitives)).toBe(true);
      expect(display._groundPrimitives.contains(source._groundPrimitives)).toBe(
        true
      );
    });

    it("cleans up primitives on dataSource removed", function () {
      var source = new MockDataSource();

      display = new DataSourceDisplay({
        scene: scene,
        dataSourceCollection: dataSourceCollection,
        visualizersCallback: visualizersCallback,
      });

      dataSourceCollection.add(source);

      expect(display._primitives.contains(source._primitives)).toBe(true);
      expect(display._groundPrimitives.contains(source._groundPrimitives)).toBe(
        true
      );

      dataSourceCollection.remove(source);

      expect(display._primitives.length).toBe(1);
      expect(display._groundPrimitives.length).toBe(1);
    });

    it("raises primitives on dataSource raise", function () {
      var source1 = new MockDataSource();
      var source2 = new MockDataSource();
      var source3 = new MockDataSource();

      display = new DataSourceDisplay({
        scene: scene,
        dataSourceCollection: dataSourceCollection,
        visualizersCallback: visualizersCallback,
      });

      dataSourceCollection.add(source1);
      dataSourceCollection.add(source2);
      dataSourceCollection.add(source3);

      dataSourceCollection.raise(source1);

      expect(display._primitives.get(1)).toBe(source2._primitives);
      expect(display._primitives.get(2)).toBe(source1._primitives);
      expect(display._primitives.get(3)).toBe(source3._primitives);
    });

    it("lowers primitives on dataSource lower", function () {
      var source1 = new MockDataSource();
      var source2 = new MockDataSource();
      var source3 = new MockDataSource();

      display = new DataSourceDisplay({
        scene: scene,
        dataSourceCollection: dataSourceCollection,
        visualizersCallback: visualizersCallback,
      });

      dataSourceCollection.add(source1);
      dataSourceCollection.add(source2);
      dataSourceCollection.add(source3);

      dataSourceCollection.lower(source3);

      expect(display._primitives.get(1)).toBe(source1._primitives);
      expect(display._primitives.get(2)).toBe(source3._primitives);
      expect(display._primitives.get(3)).toBe(source2._primitives);
    });

    it("raises primitives to top on dataSource raiseToTop", function () {
      var source1 = new MockDataSource();
      var source2 = new MockDataSource();
      var source3 = new MockDataSource();

      display = new DataSourceDisplay({
        scene: scene,
        dataSourceCollection: dataSourceCollection,
        visualizersCallback: visualizersCallback,
      });

      dataSourceCollection.add(source1);
      dataSourceCollection.add(source2);
      dataSourceCollection.add(source3);

      dataSourceCollection.raiseToTop(source1);

      expect(display._primitives.get(1)).toBe(source2._primitives);
      expect(display._primitives.get(2)).toBe(source3._primitives);
      expect(display._primitives.get(3)).toBe(source1._primitives);
    });

    it("lowers primitives to bottom on dataSource lowerToBottom", function () {
      var source1 = new MockDataSource();
      var source2 = new MockDataSource();
      var source3 = new MockDataSource();

      display = new DataSourceDisplay({
        scene: scene,
        dataSourceCollection: dataSourceCollection,
        visualizersCallback: visualizersCallback,
      });

      dataSourceCollection.add(source1);
      dataSourceCollection.add(source2);
      dataSourceCollection.add(source3);

      dataSourceCollection.lowerToBottom(source3);

      expect(display._primitives.get(1)).toBe(source3._primitives);
      expect(display._primitives.get(2)).toBe(source1._primitives);
      expect(display._primitives.get(3)).toBe(source2._primitives);
    });

    it("adds primitives to scene when dataSource is added to the collection", function () {
      display = new DataSourceDisplay({
        scene: scene,
        dataSourceCollection: dataSourceCollection,
        visualizersCallback: visualizersCallback,
      });
      expect(scene.primitives.contains(display._primitives)).toBe(false);
      expect(scene.groundPrimitives.contains(display._groundPrimitives)).toBe(
        false
      );

      dataSourceCollection.add(new MockDataSource());

      expect(scene.primitives.contains(display._primitives)).toBe(true);
      expect(scene.groundPrimitives.contains(display._groundPrimitives)).toBe(
        true
      );
    });

    it("adds primitives to scene if dataSourceCollection is not empty", function () {
      dataSourceCollection.add(new MockDataSource());
      display = new DataSourceDisplay({
        scene: scene,
        dataSourceCollection: dataSourceCollection,
        visualizersCallback: visualizersCallback,
      });

      expect(scene.primitives.contains(display._primitives)).toBe(true);
      expect(scene.groundPrimitives.contains(display._groundPrimitives)).toBe(
        true
      );
    });

    it("adds primitives to the scene when entities are added to the default dataSource", function () {
      display = new DataSourceDisplay({
        scene: scene,
        dataSourceCollection: dataSourceCollection,
        visualizersCallback: visualizersCallback,
      });
      expect(scene.primitives.contains(display._primitives)).toBe(false);
      expect(scene.groundPrimitives.contains(display._groundPrimitives)).toBe(
        false
      );

      display.defaultDataSource.entities.add(new Entity());

      expect(scene.primitives.contains(display._primitives)).toBe(true);
      expect(scene.groundPrimitives.contains(display._groundPrimitives)).toBe(
        true
      );
    });
  },
  "WebGL"
);