import { ApproximateTerrainHeights } from "../../Source/Cesium.js";
import { Cartesian2 } from "../../Source/Cesium.js";
import { Cartesian3 } from "../../Source/Cesium.js";
import { Color } from "../../Source/Cesium.js";
import { DistanceDisplayCondition } from "../../Source/Cesium.js";
import { JulianDate } from "../../Source/Cesium.js";
import { Math as CesiumMath } from "../../Source/Cesium.js";
import { TimeInterval } from "../../Source/Cesium.js";
import { TimeIntervalCollection } from "../../Source/Cesium.js";
import { ConstantProperty } from "../../Source/Cesium.js";
import { EllipseGeometryUpdater } from "../../Source/Cesium.js";
import { EllipseGraphics } from "../../Source/Cesium.js";
import { Entity } from "../../Source/Cesium.js";
import { GridMaterialProperty } from "../../Source/Cesium.js";
import { StaticGroundGeometryPerMaterialBatch } from "../../Source/Cesium.js";
import { TimeIntervalCollectionProperty } from "../../Source/Cesium.js";
import { ClassificationType } from "../../Source/Cesium.js";
import { GroundPrimitive } from "../../Source/Cesium.js";
import { MaterialAppearance } from "../../Source/Cesium.js";
import createScene from "../createScene.js";
import pollToPromise from "../pollToPromise.js";

describe("DataSources/StaticGroundGeometryPerMaterialBatch", function () {
  var time = JulianDate.now();
  var scene;
  beforeAll(function () {
    scene = createScene();

    return GroundPrimitive.initializeTerrainHeights();
  });

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

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

  it("handles shared material being invalidated with geometry", function () {
    if (
      !GroundPrimitive.isSupported(scene) ||
      !GroundPrimitive.supportsMaterials(scene)
    ) {
      // Don't fail if materials on GroundPrimitive not supported
      return;
    }

    var batch = new StaticGroundGeometryPerMaterialBatch(
      scene.primitives,
      ClassificationType.BOTH,
      MaterialAppearance
    );

    var ellipse = new EllipseGraphics();
    ellipse.semiMajorAxis = new ConstantProperty(2);
    ellipse.semiMinorAxis = new ConstantProperty(1);
    ellipse.material = new GridMaterialProperty();

    var entity = new Entity({
      position: new Cartesian3(1234, 5678, 9101112),
      ellipse: ellipse,
    });

    var ellipse2 = new EllipseGraphics();
    ellipse2.semiMajorAxis = new ConstantProperty(3);
    ellipse2.semiMinorAxis = new ConstantProperty(2);
    ellipse2.material = new GridMaterialProperty();

    var entity2 = new Entity({
      position: new Cartesian3(123, 456, 789),
      ellipse: ellipse2,
    });

    var updater = new EllipseGeometryUpdater(entity, scene);
    var updater2 = new EllipseGeometryUpdater(entity2, scene);
    batch.add(time, updater);
    batch.add(time, updater2);

    return pollToPromise(function () {
      scene.initializeFrame();
      var isUpdated = batch.update(time);
      scene.render(time);
      return isUpdated;
    })
      .then(function () {
        expect(scene.primitives.length).toEqual(1);
        ellipse.material.cellAlpha = new ConstantProperty(0.5);

        return pollToPromise(function () {
          scene.initializeFrame();
          var isUpdated = batch.update(time);
          scene.render(time);
          return isUpdated;
        });
      })
      .then(function () {
        expect(scene.primitives.length).toEqual(2);
        batch.removeAllPrimitives();
      });
  });

  it("updates with sampled distance display condition out of range", function () {
    if (
      !GroundPrimitive.isSupported(scene) ||
      !GroundPrimitive.supportsMaterials(scene)
    ) {
      // Don't fail if materials on GroundPrimitive not supported
      return;
    }

    var validTime = JulianDate.fromIso8601("2018-02-14T04:10:00+1100");
    var outOfRangeTime = JulianDate.fromIso8601("2018-02-14T04:20:00+1100");
    var ddc = new TimeIntervalCollectionProperty();
    ddc.intervals.addInterval(
      TimeInterval.fromIso8601({
        iso8601: "2018-02-14T04:00:00+1100/2018-02-14T04:15:00+1100",
        data: new DistanceDisplayCondition(1.0, 2.0),
      })
    );
    var entity = new Entity({
      availability: new TimeIntervalCollection([
        TimeInterval.fromIso8601({
          iso8601: "2018-02-14T04:00:00+1100/2018-02-14T04:30:00+1100",
        }),
      ]),
      position: new Cartesian3(1234, 5678, 9101112),
      ellipse: {
        semiMajorAxis: 2,
        semiMinorAxis: 1,
        material: new GridMaterialProperty(),
        distanceDisplayCondition: ddc,
      },
    });

    var batch = new StaticGroundGeometryPerMaterialBatch(
      scene.primitives,
      ClassificationType.BOTH,
      MaterialAppearance
    );

    var updater = new EllipseGeometryUpdater(entity, scene);
    batch.add(validTime, updater);

    return pollToPromise(function () {
      scene.initializeFrame();
      var isUpdated = batch.update(validTime);
      scene.render(validTime);
      return isUpdated;
    }).then(function () {
      expect(scene.primitives.length).toEqual(1);
      var primitive = scene.primitives.get(0);
      var attributes = primitive.getGeometryInstanceAttributes(entity);
      expect(attributes.distanceDisplayCondition).toEqualEpsilon(
        [1.0, 2.0],
        CesiumMath.EPSILON6
      );

      batch.update(outOfRangeTime);
      scene.render(outOfRangeTime);

      primitive = scene.primitives.get(0);
      attributes = primitive.getGeometryInstanceAttributes(entity);
      expect(attributes.distanceDisplayCondition).toEqual([0.0, Infinity]);

      batch.removeAllPrimitives();
    });
  });

  it("updates with sampled show out of range", function () {
    if (
      !GroundPrimitive.isSupported(scene) ||
      !GroundPrimitive.supportsMaterials(scene)
    ) {
      // Don't fail if materials on GroundPrimitive not supported
      return;
    }

    var validTime = JulianDate.fromIso8601("2018-02-14T04:10:00+1100");
    var outOfRangeTime = JulianDate.fromIso8601("2018-02-14T04:20:00+1100");
    var show = new TimeIntervalCollectionProperty();
    show.intervals.addInterval(
      TimeInterval.fromIso8601({
        iso8601: "2018-02-14T04:00:00+1100/2018-02-14T04:15:00+1100",
        data: true,
      })
    );
    var entity = new Entity({
      availability: new TimeIntervalCollection([
        TimeInterval.fromIso8601({
          iso8601: "2018-02-14T04:00:00+1100/2018-02-14T04:30:00+1100",
        }),
      ]),
      position: new Cartesian3(1234, 5678, 9101112),
      ellipse: {
        semiMajorAxis: 2,
        semiMinorAxis: 1,
        material: new GridMaterialProperty(),
        show: show,
      },
    });

    var batch = new StaticGroundGeometryPerMaterialBatch(
      scene.primitives,
      ClassificationType.BOTH,
      MaterialAppearance
    );

    var updater = new EllipseGeometryUpdater(entity, scene);
    batch.add(validTime, updater);

    return pollToPromise(function () {
      scene.initializeFrame();
      var isUpdated = batch.update(validTime);
      scene.render(validTime);
      return isUpdated;
    }).then(function () {
      expect(scene.primitives.length).toEqual(1);
      var primitive = scene.primitives.get(0);
      var attributes = primitive.getGeometryInstanceAttributes(entity);
      expect(attributes.show).toEqual([1]);

      batch.update(outOfRangeTime);
      scene.render(outOfRangeTime);

      primitive = scene.primitives.get(0);
      attributes = primitive.getGeometryInstanceAttributes(entity);
      expect(attributes.show).toEqual([0]);

      batch.removeAllPrimitives();
    });
  });

  it("shows only one primitive while rebuilding primitive", function () {
    if (
      !GroundPrimitive.isSupported(scene) ||
      !GroundPrimitive.supportsMaterials(scene)
    ) {
      // Don't fail if materials on GroundPrimitive not supported
      return;
    }

    var batch = new StaticGroundGeometryPerMaterialBatch(
      scene.primitives,
      ClassificationType.BOTH,
      MaterialAppearance
    );

    function buildEntity(x, y, z) {
      var material = new GridMaterialProperty({
        color: Color.YELLOW,
        cellAlpha: 0.3,
        lineCount: new Cartesian2(8, 8),
        lineThickness: new Cartesian2(2.0, 2.0),
      });

      return new Entity({
        position: new Cartesian3(x, y, z),
        ellipse: {
          semiMajorAxis: 2,
          semiMinorAxis: 1,
          material: material,
        },
      });
    }

    function renderScene() {
      scene.initializeFrame();
      var isUpdated = batch.update(time);
      scene.render(time);
      return isUpdated;
    }

    var entity1 = buildEntity(1234, 5678, 9101112);
    var entity2 = buildEntity(123, 456, 789);

    var updater1 = new EllipseGeometryUpdater(entity1, scene);
    var updater2 = new EllipseGeometryUpdater(entity2, scene);

    batch.add(time, updater1);
    return pollToPromise(renderScene)
      .then(function () {
        expect(scene.primitives.length).toEqual(1);
        var primitive = scene.primitives.get(0);
        expect(primitive.show).toBeTruthy();
      })
      .then(function () {
        batch.add(time, updater2);
      })
      .then(function () {
        return pollToPromise(function () {
          renderScene();
          return scene.primitives.length === 2;
        });
      })
      .then(function () {
        var showCount = 0;
        expect(scene.primitives.length).toEqual(2);
        showCount += !!scene.primitives.get(0).show;
        showCount += !!scene.primitives.get(1).show;
        expect(showCount).toEqual(1);
      })
      .then(function () {
        return pollToPromise(renderScene);
      })
      .then(function () {
        expect(scene.primitives.length).toEqual(1);
        var primitive = scene.primitives.get(0);
        expect(primitive.show).toBeTruthy();

        batch.removeAllPrimitives();
      });
  });

  it("batches overlapping entities with the same material separately", function () {
    if (
      !GroundPrimitive.isSupported(scene) ||
      !GroundPrimitive.supportsMaterials(scene)
    ) {
      // Don't fail if materials on GroundPrimitive not supported
      return;
    }

    var batch = new StaticGroundGeometryPerMaterialBatch(
      scene.primitives,
      ClassificationType.BOTH,
      MaterialAppearance
    );

    var ellipse = new EllipseGraphics();
    ellipse.semiMajorAxis = new ConstantProperty(2);
    ellipse.semiMinorAxis = new ConstantProperty(1);
    ellipse.material = new GridMaterialProperty();

    var entity = new Entity({
      position: new Cartesian3(1234, 5678, 9101112),
      ellipse: ellipse,
    });

    var ellipse2 = new EllipseGraphics();
    ellipse2.semiMajorAxis = new ConstantProperty(3);
    ellipse2.semiMinorAxis = new ConstantProperty(2);
    ellipse2.material = new GridMaterialProperty();

    var entity2 = new Entity({
      position: new Cartesian3(1234, 5678, 9101112),
      ellipse: ellipse2,
    });

    var updater = new EllipseGeometryUpdater(entity, scene);
    var updater2 = new EllipseGeometryUpdater(entity2, scene);
    batch.add(time, updater);
    batch.add(time, updater2);

    return pollToPromise(function () {
      scene.initializeFrame();
      var isUpdated = batch.update(time);
      scene.render(time);
      return isUpdated;
    }).then(function () {
      expect(scene.primitives.length).toEqual(2);

      batch.removeAllPrimitives();
    });
  });

  it("batches nonoverlapping entities that both use color materials", function () {
    if (
      !GroundPrimitive.isSupported(scene) ||
      !GroundPrimitive.supportsMaterials(scene)
    ) {
      // Don't fail if materials on GroundPrimitive not supported
      return;
    }

    var batch = new StaticGroundGeometryPerMaterialBatch(
      scene.primitives,
      ClassificationType.BOTH,
      MaterialAppearance
    );
    var entity = new Entity({
      position: new Cartesian3(1234, 5678, 9101112),
      ellipse: {
        semiMajorAxis: 2,
        semiMinorAxis: 1,
        material: Color.RED,
      },
    });

    var entity2 = new Entity({
      position: new Cartesian3(123, 456, 789),
      ellipse: {
        semiMajorAxis: 2,
        semiMinorAxis: 1,
        material: Color.BLUE,
      },
    });

    var updater = new EllipseGeometryUpdater(entity, scene);
    var updater2 = new EllipseGeometryUpdater(entity2, scene);
    batch.add(time, updater);
    batch.add(time, updater2);

    return pollToPromise(function () {
      scene.initializeFrame();
      var isUpdated = batch.update(time);
      scene.render(time);
      return isUpdated;
    }).then(function () {
      expect(scene.primitives.length).toEqual(1);

      batch.removeAllPrimitives();
    });
  });

  it("has correct show attribute after rebuilding primitive", function () {
    if (!GroundPrimitive.isSupported(scene)) {
      return;
    }

    if (
      !GroundPrimitive.isSupported(scene) ||
      !GroundPrimitive.supportsMaterials(scene)
    ) {
      // Don't fail if materials on GroundPrimitive not supported
      return;
    }

    var batch = new StaticGroundGeometryPerMaterialBatch(
      scene.primitives,
      ClassificationType.BOTH,
      MaterialAppearance
    );

    function buildEntity(x, y, z) {
      var material = new GridMaterialProperty({
        color: Color.YELLOW,
        cellAlpha: 0.3,
        lineCount: new Cartesian2(8, 8),
        lineThickness: new Cartesian2(2.0, 2.0),
      });

      return new Entity({
        position: new Cartesian3(x, y, z),
        ellipse: {
          semiMajorAxis: 2,
          semiMinorAxis: 1,
          material: material,
        },
      });
    }

    function renderScene() {
      scene.initializeFrame();
      var isUpdated = batch.update(time);
      scene.render(time);
      return isUpdated;
    }

    var entity1 = buildEntity(1234, 5678, 9101112);
    var updater1 = new EllipseGeometryUpdater(entity1, scene);
    batch.add(time, updater1);

    var entity2 = buildEntity(123, 456, 789);
    var updater2 = new EllipseGeometryUpdater(entity2, scene);

    return pollToPromise(renderScene)
      .then(function () {
        expect(scene.primitives.length).toEqual(1);
        var primitive = scene.primitives.get(0);
        var attributes = primitive.getGeometryInstanceAttributes(entity1);
        expect(attributes.show).toEqual([1]);

        entity1.show = false;
        updater1._onEntityPropertyChanged(entity1, "isShowing");
        return pollToPromise(renderScene);
      })
      .then(function () {
        expect(scene.primitives.length).toEqual(1);
        var primitive = scene.primitives.get(0);
        var attributes = primitive.getGeometryInstanceAttributes(entity1);
        expect(attributes.show).toEqual([0]);

        batch.add(time, updater2);
        return pollToPromise(renderScene);
      })
      .then(function () {
        expect(scene.primitives.length).toEqual(1);
        var primitive = scene.primitives.get(0);
        var attributes = primitive.getGeometryInstanceAttributes(entity1);
        expect(attributes.show).toEqual([0]);

        attributes = primitive.getGeometryInstanceAttributes(entity2);
        expect(attributes.show).toEqual([1]);

        batch.removeAllPrimitives();
      });
  });
});
