import { ApproximateTerrainHeights } from "../../Source/Cesium.js";
import { BoundingSphere } from "../../Source/Cesium.js";
import { Cartesian3 } from "../../Source/Cesium.js";
import { Color } from "../../Source/Cesium.js";
import { defined } 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 { BoundingSphereState } from "../../Source/Cesium.js";
import { ColorMaterialProperty } from "../../Source/Cesium.js";
import { ConstantProperty } from "../../Source/Cesium.js";
import { Entity } from "../../Source/Cesium.js";
import { PolylineGeometryUpdater } from "../../Source/Cesium.js";
import { PolylineGraphics } from "../../Source/Cesium.js";
import { PolylineOutlineMaterialProperty } from "../../Source/Cesium.js";
import { StaticGroundPolylinePerMaterialBatch } from "../../Source/Cesium.js";
import { TimeIntervalCollectionProperty } from "../../Source/Cesium.js";
import { ClassificationType } from "../../Source/Cesium.js";
import { GroundPolylinePrimitive } from "../../Source/Cesium.js";
import createScene from "../createScene.js";
import pollToPromise from "../pollToPromise.js";

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

    return GroundPolylinePrimitive.initializeTerrainHeights();
  });

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

    GroundPolylinePrimitive._initPromise = undefined;
    GroundPolylinePrimitive._initialized = false;

    ApproximateTerrainHeights._initPromise = undefined;
    ApproximateTerrainHeights._terrainHeights = undefined;
  });

  afterEach(function () {
    if (defined(batch)) {
      batch.removeAllPrimitives();
      batch = undefined;
    }
  });

  function createGroundPolyline() {
    var polyline = new PolylineGraphics();
    polyline.clampToGround = new ConstantProperty(true);
    polyline.positions = new ConstantProperty(
      Cartesian3.fromDegreesArray([0, 0, 0.1, 0, 0.1, 0.1, 0, 0.1])
    );
    return polyline;
  }

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

    batch = new StaticGroundPolylinePerMaterialBatch(
      scene.groundPrimitives,
      ClassificationType.BOTH,
      false
    );

    var polyline1 = createGroundPolyline();
    polyline1.material = new PolylineOutlineMaterialProperty();

    var entity = new Entity({
      polyline: polyline1,
    });

    var polyline2 = createGroundPolyline();
    polyline2.material = new PolylineOutlineMaterialProperty();

    var entity2 = new Entity({
      polyline: polyline2,
    });

    var updater = new PolylineGeometryUpdater(entity, scene);
    var updater2 = new PolylineGeometryUpdater(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.groundPrimitives.length).toEqual(1);
      polyline1.material.outlineWidth = new ConstantProperty(0.5);

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

  it("updates with sampled color out of range", function () {
    if (!GroundPolylinePrimitive.isSupported(scene)) {
      // Don't fail if GroundPolylinePrimitive is not supported
      return;
    }

    var validTime = JulianDate.fromIso8601("2018-02-14T04:10:00+1100");
    var color = new TimeIntervalCollectionProperty();
    color.intervals.addInterval(
      TimeInterval.fromIso8601({
        iso8601: "2018-02-14T04:00:00+1100/2018-02-14T04:15:00+1100",
        data: Color.RED,
      })
    );
    var polyline = createGroundPolyline();
    polyline.material = new ColorMaterialProperty(color);
    var entity = new Entity({
      availability: new TimeIntervalCollection([
        TimeInterval.fromIso8601({
          iso8601: "2018-02-14T04:00:00+1100/2018-02-14T04:30:00+1100",
        }),
      ]),
      polyline: polyline,
    });

    batch = new StaticGroundPolylinePerMaterialBatch(
      scene.groundPrimitives,
      ClassificationType.BOTH,
      false
    );

    var updater = new PolylineGeometryUpdater(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.groundPrimitives.length).toEqual(1);
      var primitive = scene.groundPrimitives.get(0);
      var attributes = primitive.getGeometryInstanceAttributes(entity);
      expect(attributes.color).toEqual([255, 0, 0, 255]);

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

      primitive = scene.groundPrimitives.get(0);
      attributes = primitive.getGeometryInstanceAttributes(entity);
      expect(attributes.color).toEqual([255, 255, 255, 255]);

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

  it("updates with sampled distance display condition out of range", function () {
    if (!GroundPolylinePrimitive.isSupported(scene)) {
      // Don't fail if GroundPolylinePrimitive is 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 polyline = createGroundPolyline();
    polyline.distanceDisplayCondition = ddc;
    var entity = new Entity({
      availability: new TimeIntervalCollection([
        TimeInterval.fromIso8601({
          iso8601: "2018-02-14T04:00:00+1100/2018-02-14T04:30:00+1100",
        }),
      ]),
      polyline: polyline,
    });

    batch = new StaticGroundPolylinePerMaterialBatch(
      scene.groundPrimitives,
      ClassificationType.BOTH,
      false
    );

    var updater = new PolylineGeometryUpdater(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.groundPrimitives.length).toEqual(1);
      var primitive = scene.groundPrimitives.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.groundPrimitives.get(0);
      attributes = primitive.getGeometryInstanceAttributes(entity);
      expect(attributes.distanceDisplayCondition).toEqual([0.0, Infinity]);

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

  it("updates with sampled color out of range", function () {
    if (!GroundPolylinePrimitive.isSupported(scene)) {
      // Don't fail if GroundPolylinePrimitive is 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 polyline = createGroundPolyline();
    polyline.show = show;
    var entity = new Entity({
      availability: new TimeIntervalCollection([
        TimeInterval.fromIso8601({
          iso8601: "2018-02-14T04:00:00+1100/2018-02-14T04:30:00+1100",
        }),
      ]),
      polyline: polyline,
    });

    batch = new StaticGroundPolylinePerMaterialBatch(
      scene.groundPrimitives,
      false
    );

    var updater = new PolylineGeometryUpdater(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.groundPrimitives.length).toEqual(1);
      var primitive = scene.groundPrimitives.get(0);
      var attributes = primitive.getGeometryInstanceAttributes(entity);
      expect(attributes.show).toEqual([1]);

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

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

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

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

    batch = new StaticGroundPolylinePerMaterialBatch(
      scene.groundPrimitives,
      ClassificationType.BOTH,
      false
    );

    function buildEntity() {
      var polyline = createGroundPolyline();
      polyline.material = new PolylineOutlineMaterialProperty({
        color: Color.ORANGE,
        outlineWidth: 2,
        outlineColor: Color.BLACK,
      });

      return new Entity({
        polyline: polyline,
      });
    }

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

    var entity1 = buildEntity();
    var entity2 = buildEntity();

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

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

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

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

    batch = new StaticGroundPolylinePerMaterialBatch(
      scene.groundPrimitives,
      ClassificationType.BOTH,
      false
    );
    var polyline1 = createGroundPolyline();
    polyline1.material = Color.RED;
    var entity = new Entity({
      polyline: polyline1,
    });

    var polyline2 = createGroundPolyline();
    polyline2.material = Color.RED;
    var entity2 = new Entity({
      polyline: polyline2,
    });

    var updater = new PolylineGeometryUpdater(entity, scene);
    var updater2 = new PolylineGeometryUpdater(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.groundPrimitives.length).toEqual(1);

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

  it("batches entities with the same material but different Z indices separately", function () {
    if (!GroundPolylinePrimitive.isSupported(scene)) {
      // Don't fail if GroundPolylinePrimitive is not supported
      return;
    }

    batch = new StaticGroundPolylinePerMaterialBatch(
      scene.groundPrimitives,
      ClassificationType.BOTH,
      false
    );

    var polyline1 = createGroundPolyline();
    polyline1.material = new PolylineOutlineMaterialProperty();
    polyline1.zIndex = 0;

    var entity = new Entity({
      polyline: polyline1,
    });

    var polyline2 = createGroundPolyline();
    polyline2.material = new PolylineOutlineMaterialProperty();
    polyline2.zIndex = 1;

    var entity2 = new Entity({
      polyline: polyline2,
    });

    var updater = new PolylineGeometryUpdater(entity, scene);
    var updater2 = new PolylineGeometryUpdater(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.groundPrimitives.length).toEqual(2);

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

  it("removes entities", function () {
    if (!GroundPolylinePrimitive.isSupported(scene)) {
      // Don't fail if GroundPolylinePrimitive is not supported
      return;
    }

    batch = new StaticGroundPolylinePerMaterialBatch(
      scene.groundPrimitives,
      ClassificationType.BOTH,
      false
    );

    var polyline1 = createGroundPolyline();
    polyline1.material = new PolylineOutlineMaterialProperty();

    var entity = new Entity({
      polyline: polyline1,
    });

    var updater = new PolylineGeometryUpdater(entity, scene);
    batch.add(time, updater);

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

        batch.remove(updater);

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

  it("gets bounding spheres", function () {
    if (!GroundPolylinePrimitive.isSupported(scene)) {
      // Don't fail if GroundPolylinePrimitive is not supported
      return;
    }

    var resultSphere = new BoundingSphere();
    batch = new StaticGroundPolylinePerMaterialBatch(
      scene.groundPrimitives,
      ClassificationType.BOTH,
      false
    );

    var polyline1 = createGroundPolyline();
    polyline1.material = new PolylineOutlineMaterialProperty();

    var entity = new Entity({
      polyline: polyline1,
    });

    var updater = new PolylineGeometryUpdater(entity, scene);

    var state = batch.getBoundingSphere(updater, resultSphere);
    expect(state).toEqual(BoundingSphereState.FAILED);

    batch.add(time, updater);

    batch.update(time);
    state = batch.getBoundingSphere(updater, resultSphere);
    expect(state).toEqual(BoundingSphereState.PENDING);

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

      state = batch.getBoundingSphere(updater, resultSphere);
      expect(state).toEqual(BoundingSphereState.DONE);

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

  it("has correct show attribute after rebuilding primitive", function () {
    if (!GroundPolylinePrimitive.isSupported(scene)) {
      // Don't fail if GroundPolylinePrimitive is not supported
      return;
    }
    batch = new StaticGroundPolylinePerMaterialBatch(
      scene.groundPrimitives,
      ClassificationType.BOTH,
      false
    );

    function buildEntity() {
      var polyline = createGroundPolyline();
      polyline.material = new PolylineOutlineMaterialProperty({
        color: Color.ORANGE,
        outlineWidth: 2,
        outlineColor: Color.BLACK,
      });

      return new Entity({
        polyline: polyline,
      });
    }

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

    var entity1 = buildEntity();
    var updater1 = new PolylineGeometryUpdater(entity1, scene);
    batch.add(time, updater1);

    var entity2 = buildEntity();
    var updater2 = new PolylineGeometryUpdater(entity2, scene);

    return pollToPromise(renderScene)
      .then(function () {
        expect(scene.groundPrimitives.length).toEqual(1);
        var primitive = scene.groundPrimitives.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.groundPrimitives.length).toEqual(1);
        var primitive = scene.groundPrimitives.get(0);
        var attributes = primitive.getGeometryInstanceAttributes(entity1);
        expect(attributes.show).toEqual([0]);

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

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

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