import { Iso8601 } from "../../Source/Cesium.js";
import { JulianDate } from "../../Source/Cesium.js";
import { TimeInterval } from "../../Source/Cesium.js";
import { TimeIntervalCollection } from "../../Source/Cesium.js";
import { Entity } from "../../Source/Cesium.js";
import { EntityCollection } from "../../Source/Cesium.js";

describe("DataSources/EntityCollection", function () {
  function CollectionListener() {
    this.timesCalled = 0;
    this.added = undefined;
    this.removed = undefined;
    this.changed = undefined;
  }
  CollectionListener.prototype.onCollectionChanged = function (
    collection,
    added,
    removed,
    changed
  ) {
    this.timesCalled++;
    this.added = added.slice(0);
    this.removed = removed.slice(0);
    this.changed = changed.slice(0);
  };

  it("constructor has expected defaults", function () {
    var entityCollection = new EntityCollection();
    expect(entityCollection.id).toBeDefined();
    expect(entityCollection.values.length).toEqual(0);
  });

  it("add/remove works", function () {
    var entity = new Entity();
    var entity2 = new Entity();
    var entityCollection = new EntityCollection();

    entityCollection.add(entity);
    expect(entityCollection.values.length).toEqual(1);

    entityCollection.add(entity2);
    expect(entityCollection.values.length).toEqual(2);

    entityCollection.remove(entity2);
    expect(entityCollection.values.length).toEqual(1);

    entityCollection.remove(entity);
    expect(entityCollection.values.length).toEqual(0);
  });

  it("add sets entityCollection on entity", function () {
    var entity = new Entity();
    var entityCollection = new EntityCollection();

    entityCollection.add(entity);
    expect(entity.entityCollection).toBe(entityCollection);
  });

  it("Entity.isShowing changes when collection show changes.", function () {
    var entity = new Entity();
    var entityCollection = new EntityCollection();

    entityCollection.add(entity);

    expect(entity.isShowing).toBe(true);

    var listener = jasmine.createSpy("listener");
    entity.definitionChanged.addEventListener(listener);

    entityCollection.show = false;

    expect(listener.calls.count()).toBe(1);
    expect(listener.calls.argsFor(0)).toEqual([
      entity,
      "isShowing",
      false,
      true,
    ]);
    expect(entity.isShowing).toBe(false);
  });

  it("add with template", function () {
    var entityCollection = new EntityCollection();

    var entity = entityCollection.add({
      id: "1",
    });

    expect(entityCollection.values.length).toEqual(1);
    expect(entity.id).toBe("1");
    expect(entity.constructor).toBe(Entity);
  });

  it("add/remove raises expected events", function () {
    var entity = new Entity();
    var entity2 = new Entity();
    var entityCollection = new EntityCollection();

    var listener = new CollectionListener();
    entityCollection.collectionChanged.addEventListener(
      listener.onCollectionChanged,
      listener
    );

    entityCollection.add(entity);
    expect(listener.timesCalled).toEqual(1);
    expect(listener.added.length).toEqual(1);
    expect(listener.added[0]).toBe(entity);
    expect(listener.removed.length).toEqual(0);
    expect(listener.changed.length).toEqual(0);

    entity.name = "newName";
    expect(listener.timesCalled).toEqual(2);
    expect(listener.added.length).toEqual(0);
    expect(listener.removed.length).toEqual(0);
    expect(listener.changed.length).toEqual(1);
    expect(listener.changed[0]).toBe(entity);

    entityCollection.add(entity2);
    expect(listener.timesCalled).toEqual(3);
    expect(listener.added.length).toEqual(1);
    expect(listener.added[0]).toBe(entity2);
    expect(listener.removed.length).toEqual(0);
    expect(listener.changed.length).toEqual(0);

    entityCollection.remove(entity2);
    expect(listener.timesCalled).toEqual(4);
    expect(listener.added.length).toEqual(0);
    expect(listener.removed.length).toEqual(1);
    expect(listener.removed[0]).toBe(entity2);
    expect(listener.changed.length).toEqual(0);

    entityCollection.remove(entity);
    expect(listener.timesCalled).toEqual(5);
    expect(listener.added.length).toEqual(0);
    expect(listener.removed.length).toEqual(1);
    expect(listener.removed[0]).toBe(entity);

    entityCollection.collectionChanged.removeEventListener(
      listener.onCollectionChanged,
      listener
    );
  });

  it("raises expected events when reentrant", function () {
    var entityCollection = new EntityCollection();

    var entity = new Entity();
    var entity2 = new Entity();
    entityCollection.add(entity);
    entityCollection.add(entity2);

    var entityToDelete = new Entity();
    entityCollection.add(entityToDelete);

    var entityToAdd = new Entity();

    var inCallback = false;
    var listener = jasmine
      .createSpy("listener")
      .and.callFake(function (collection, added, removed, changed) {
        //When we set the name to `newName` below, this code will modify entity2's name, thus triggering
        //another event firing that occurs after all current subscribers have been notified of the
        //event we are inside of.

        //By checking that inCallback is false, we are making sure the entity2.name assignment
        //is delayed until after the first round of events is fired.
        expect(inCallback).toBe(false);
        inCallback = true;
        if (entity2.name !== "Bob") {
          entity2.name = "Bob";
        }
        if (entityCollection.contains(entityToDelete)) {
          entityCollection.removeById(entityToDelete.id);
        }
        if (!entityCollection.contains(entityToAdd)) {
          entityCollection.add(entityToAdd);
        }
        inCallback = false;
      });
    entityCollection.collectionChanged.addEventListener(listener);

    entity.name = "newName";
    expect(listener.calls.count()).toBe(2);
    expect(listener.calls.argsFor(0)).toEqual([
      entityCollection,
      [],
      [],
      [entity],
    ]);
    expect(listener.calls.argsFor(1)).toEqual([
      entityCollection,
      [entityToAdd],
      [entityToDelete],
      [entity2],
    ]);

    expect(entity.name).toEqual("newName");
    expect(entity2.name).toEqual("Bob");
    expect(entityCollection.contains(entityToDelete)).toEqual(false);
    expect(entityCollection.contains(entityToAdd)).toEqual(true);
  });

  it("suspended add/remove raises expected events", function () {
    var entity = new Entity();
    var entity2 = new Entity();
    var entity3 = new Entity();

    var entityCollection = new EntityCollection();

    var listener = new CollectionListener();
    entityCollection.collectionChanged.addEventListener(
      listener.onCollectionChanged,
      listener
    );

    entityCollection.suspendEvents();
    entityCollection.suspendEvents();
    entityCollection.add(entity);
    entityCollection.add(entity2);
    entityCollection.add(entity3);
    entity2.name = "newName2";
    entity3.name = "newName3";
    entityCollection.remove(entity2);

    expect(listener.timesCalled).toEqual(0);
    entityCollection.resumeEvents();

    expect(listener.timesCalled).toEqual(0);
    entityCollection.resumeEvents();

    expect(listener.timesCalled).toEqual(1);
    expect(listener.added.length).toEqual(2);
    expect(listener.added[0]).toBe(entity);
    expect(listener.added[1]).toBe(entity3);
    expect(listener.removed.length).toEqual(0);
    expect(listener.changed.length).toEqual(0);

    entityCollection.suspendEvents();
    entity.name = "newName";
    entity3.name = "newewName3";
    entityCollection.remove(entity3);
    entityCollection.resumeEvents();

    expect(listener.timesCalled).toEqual(2);
    expect(listener.added.length).toEqual(0);
    expect(listener.removed.length).toEqual(1);
    expect(listener.removed[0]).toBe(entity3);
    expect(listener.changed.length).toEqual(1);
    expect(listener.changed[0]).toBe(entity);

    entityCollection.suspendEvents();
    entityCollection.remove(entity);
    entityCollection.add(entity);
    entityCollection.resumeEvents();

    expect(listener.timesCalled).toEqual(2);

    entityCollection.collectionChanged.removeEventListener(
      listener.onCollectionChanged,
      listener
    );
  });

  it("removeAll works", function () {
    var entity = new Entity();
    var entity2 = new Entity();
    var entityCollection = new EntityCollection();

    entityCollection.add(entity);
    entityCollection.add(entity2);
    entityCollection.removeAll();
    expect(entityCollection.values.length).toEqual(0);
  });

  it("removeAll raises expected events", function () {
    var entity = new Entity();
    var entity2 = new Entity();
    var entityCollection = new EntityCollection();

    var listener = new CollectionListener();

    entityCollection.add(entity);
    entityCollection.add(entity2);

    entityCollection.collectionChanged.addEventListener(
      listener.onCollectionChanged,
      listener
    );
    entityCollection.removeAll();

    expect(listener.timesCalled).toEqual(1);
    expect(listener.removed.length).toEqual(2);
    expect(listener.removed[0]).toBe(entity);
    expect(listener.removed[1]).toBe(entity2);
    expect(listener.added.length).toEqual(0);

    entityCollection.removeAll();
    expect(listener.timesCalled).toEqual(1);

    entityCollection.collectionChanged.removeEventListener(
      listener.onCollectionChanged,
      listener
    );
  });

  it("suspended removeAll raises expected events", function () {
    var entity = new Entity();
    var entity2 = new Entity();
    var entityCollection = new EntityCollection();

    var listener = new CollectionListener();

    entityCollection.add(entity);
    entityCollection.add(entity2);

    entityCollection.collectionChanged.addEventListener(
      listener.onCollectionChanged,
      listener
    );

    entityCollection.suspendEvents();
    entity2.name = "newName";
    entityCollection.removeAll();
    entityCollection.resumeEvents();
    expect(listener.timesCalled).toEqual(1);
    expect(listener.removed.length).toEqual(2);
    expect(listener.removed[0]).toBe(entity);
    expect(listener.removed[1]).toBe(entity2);
    expect(listener.added.length).toEqual(0);
    expect(listener.changed.length).toEqual(0);

    entityCollection.suspendEvents();
    entityCollection.add(entity);
    entityCollection.add(entity2);
    entityCollection.remove(entity2);
    entityCollection.removeAll();
    entityCollection.resumeEvents();
    expect(listener.timesCalled).toEqual(1);

    entityCollection.collectionChanged.removeEventListener(
      listener.onCollectionChanged,
      listener
    );
  });

  it("removeById returns false if id not in collection.", function () {
    var entityCollection = new EntityCollection();
    expect(entityCollection.removeById("notThere")).toBe(false);
  });

  it("getById works", function () {
    var entity = new Entity();
    var entity2 = new Entity();
    var entityCollection = new EntityCollection();

    entityCollection.add(entity);
    entityCollection.add(entity2);

    expect(entityCollection.getById(entity.id)).toBe(entity);
    expect(entityCollection.getById(entity2.id)).toBe(entity2);
  });

  it("getById returns undefined for non-existent object", function () {
    var entityCollection = new EntityCollection();
    expect(entityCollection.getById("123")).toBeUndefined();
  });

  it("getOrCreateEntity creates a new object if it does not exist.", function () {
    var entityCollection = new EntityCollection();
    expect(entityCollection.values.length).toEqual(0);
    var testObject = entityCollection.getOrCreateEntity("test");
    expect(entityCollection.values.length).toEqual(1);
    expect(entityCollection.values[0]).toEqual(testObject);
  });

  it("getOrCreateEntity does not create a new object if it already exists.", function () {
    var entityCollection = new EntityCollection();
    expect(entityCollection.values.length).toEqual(0);
    var testObject = entityCollection.getOrCreateEntity("test");
    expect(entityCollection.values.length).toEqual(1);
    expect(entityCollection.values[0]).toEqual(testObject);
    var testObject2 = entityCollection.getOrCreateEntity("test");
    expect(entityCollection.values.length).toEqual(1);
    expect(entityCollection.values[0]).toEqual(testObject);
    expect(testObject2).toEqual(testObject);
  });

  it("computeAvailability returns infinite with no data.", function () {
    var entityCollection = new EntityCollection();
    var availability = entityCollection.computeAvailability();
    expect(availability.start).toEqual(Iso8601.MINIMUM_VALUE);
    expect(availability.stop).toEqual(Iso8601.MAXIMUM_VALUE);
  });

  it("computeAvailability returns intersction of collections.", function () {
    var entityCollection = new EntityCollection();

    var entity = entityCollection.getOrCreateEntity("1");
    var entity2 = entityCollection.getOrCreateEntity("2");
    var entity3 = entityCollection.getOrCreateEntity("3");

    entity.availability = new TimeIntervalCollection();
    entity.availability.addInterval(
      TimeInterval.fromIso8601({
        iso8601: "2012-08-01/2012-08-02",
      })
    );
    entity2.availability = new TimeIntervalCollection();
    entity2.availability.addInterval(
      TimeInterval.fromIso8601({
        iso8601: "2012-08-05/2012-08-06",
      })
    );
    entity3.availability = undefined;

    var availability = entityCollection.computeAvailability();
    expect(availability.start).toEqual(JulianDate.fromIso8601("2012-08-01"));
    expect(availability.stop).toEqual(JulianDate.fromIso8601("2012-08-06"));
  });

  it("computeAvailability works if only start or stop time is infinite.", function () {
    var entityCollection = new EntityCollection();

    var entity = entityCollection.getOrCreateEntity("1");
    var entity2 = entityCollection.getOrCreateEntity("2");
    var entity3 = entityCollection.getOrCreateEntity("3");

    entity.availability = new TimeIntervalCollection();
    entity.availability.addInterval(
      TimeInterval.fromIso8601({
        iso8601: "2012-08-01/9999-12-31T24:00:00Z",
      })
    );
    entity2.availability = new TimeIntervalCollection();
    entity2.availability.addInterval(
      TimeInterval.fromIso8601({
        iso8601: "0000-01-01T00:00:00Z/2012-08-06",
      })
    );
    entity3.availability = undefined;

    var availability = entityCollection.computeAvailability();
    expect(availability.start).toEqual(JulianDate.fromIso8601("2012-08-01"));
    expect(availability.stop).toEqual(JulianDate.fromIso8601("2012-08-06"));
  });

  it("resumeEvents throws if no matching suspendEvents ", function () {
    var entityCollection = new EntityCollection();
    expect(function () {
      entityCollection.resumeEvents();
    }).toThrowDeveloperError();
  });

  it("add throws with undefined Entity", function () {
    var entityCollection = new EntityCollection();
    expect(function () {
      entityCollection.add(undefined);
    }).toThrowDeveloperError();
  });

  it("add throws for Entity with same id", function () {
    var entityCollection = new EntityCollection();
    var entity = new Entity({
      id: "1",
    });
    var entity2 = new Entity({
      id: "1",
    });
    entityCollection.add(entity);

    expect(function () {
      entityCollection.add(entity2);
    }).toThrowRuntimeError();
  });

  it("contains returns true if in collection", function () {
    var entityCollection = new EntityCollection();
    var entity = entityCollection.getOrCreateEntity("asd");
    expect(entityCollection.contains(entity)).toBe(true);
  });

  it("contains returns false if not in collection", function () {
    var entityCollection = new EntityCollection();
    expect(entityCollection.contains(new Entity())).toBe(false);
  });

  it("contains throws with undefined Entity", function () {
    var entityCollection = new EntityCollection();
    expect(function () {
      entityCollection.contains(undefined);
    }).toThrowDeveloperError();
  });

  it("remove returns false with undefined Entity", function () {
    var entityCollection = new EntityCollection();
    expect(entityCollection.remove(undefined)).toBe(false);
  });

  it("removeById returns false with undefined id", function () {
    var entityCollection = new EntityCollection();
    expect(entityCollection.removeById(undefined)).toBe(false);
  });

  it("getById throws if no id specified", function () {
    var entityCollection = new EntityCollection();
    expect(function () {
      entityCollection.getById(undefined);
    }).toThrowDeveloperError();
  });

  it("getOrCreateEntity throws if no id specified", function () {
    var entityCollection = new EntityCollection();
    expect(function () {
      entityCollection.getOrCreateEntity(undefined);
    }).toThrowDeveloperError();
  });
});
