import { Cartesian3 } from "../../Source/Cesium.js";
import { Color } from "../../Source/Cesium.js";
import { Credit } from "../../Source/Cesium.js";
import { Event } from "../../Source/Cesium.js";
import { JulianDate } from "../../Source/Cesium.js";
import { PolygonHierarchy } from "../../Source/Cesium.js";
import { RuntimeError } from "../../Source/Cesium.js";
import { CallbackProperty } from "../../Source/Cesium.js";
import { ConstantProperty } from "../../Source/Cesium.js";
import { EntityCollection } from "../../Source/Cesium.js";
import { GeoJsonDataSource } from "../../Source/Cesium.js";
import { HeightReference } from "../../Source/Cesium.js";
import { when } from "../../Source/Cesium.js";

describe("DataSources/GeoJsonDataSource", function () {
  var defaultMarkerSize;
  var defaultSymbol;
  var defaultMarkerColor;
  var defaultStroke;
  var defaultStrokeWidth;
  var defaultFill;
  var defaultClampToGround;

  beforeAll(function () {
    defaultMarkerSize = GeoJsonDataSource.markerSize;
    defaultSymbol = GeoJsonDataSource.markerSymbol;
    defaultMarkerColor = GeoJsonDataSource.markerColor;
    defaultStroke = GeoJsonDataSource.stroke;
    defaultStrokeWidth = GeoJsonDataSource.strokeWidth;
    defaultFill = GeoJsonDataSource.fill;
    defaultClampToGround = GeoJsonDataSource.clampToGround;
  });

  beforeEach(function () {
    GeoJsonDataSource.markerSize = defaultMarkerSize;
    GeoJsonDataSource.markerSymbol = defaultSymbol;
    GeoJsonDataSource.markerColor = defaultMarkerColor;
    GeoJsonDataSource.stroke = defaultStroke;
    GeoJsonDataSource.strokeWidth = defaultStrokeWidth;
    GeoJsonDataSource.fill = defaultFill;
    GeoJsonDataSource.clampToGround = defaultClampToGround;
  });

  var time = new JulianDate();

  function coordinatesToCartesian(coordinates) {
    return Cartesian3.fromDegrees(
      coordinates[0],
      coordinates[1],
      coordinates[2]
    );
  }

  function coordinatesArrayToCartesian(coordinates) {
    var result = [];
    for (var i = 0; i < coordinates.length; i++) {
      result.push(coordinatesToCartesian(coordinates[i]));
    }
    return result;
  }

  function multiLineToCartesian(geometry) {
    var coordinates = geometry.coordinates;
    var result = [];
    for (var i = 0; i < coordinates.length; i++) {
      result.push(coordinatesArrayToCartesian(coordinates[i]));
    }
    return result;
  }

  function polygonCoordinatesToCartesian(coordinates) {
    return coordinatesArrayToCartesian(coordinates);
  }

  function multiPolygonCoordinatesToCartesian(coordinates) {
    var result = [];
    for (var i = 0; i < coordinates.length; i++) {
      result.push(coordinatesArrayToCartesian(coordinates[i][0]));
    }
    return result;
  }

  var point = {
    type: "Point",
    coordinates: [102.0, 0.5],
  };

  var pointNamedCrs = {
    type: "Point",
    coordinates: [102.0, 0.5],
    crs: {
      type: "name",
      properties: {
        name: "EPSG:4326",
      },
    },
  };

  var pointNamedCrsOgc = {
    type: "Point",
    coordinates: [102.0, 0.5],
    crs: {
      type: "name",
      properties: {
        name: "urn:ogc:def:crs:OGC:1.3:CRS84",
      },
    },
  };

  var pointNamedCrsEpsg = {
    type: "Point",
    coordinates: [102.0, 0.5],
    crs: {
      type: "name",
      properties: {
        name: "urn:ogc:def:crs:EPSG::4326",
      },
    },
  };

  var pointCrsLinkHref = {
    type: "Point",
    coordinates: [102.0, 0.5],
    crs: {
      type: "link",
      properties: {
        href: "http://crs.invalid",
      },
    },
  };

  var pointCrsEpsg = {
    type: "Point",
    coordinates: [102.0, 0.5],
    crs: {
      type: "EPSG",
      properties: {
        code: 4326,
      },
    },
  };

  var lineString = {
    type: "LineString",
    coordinates: [
      [100.0, 0.0],
      [101.0, 1.0],
    ],
  };

  var polygon = {
    type: "Polygon",
    coordinates: [
      [
        [100.0, 0.0],
        [101.0, 0.0],
        [101.0, 1.0],
        [100.0, 1.0],
        [100.0, 0.0],
      ],
    ],
  };

  var polygonWithHoles = {
    type: "Polygon",
    coordinates: [
      [
        [100.0, 0.0],
        [101.0, 0.0],
        [101.0, 1.0],
        [100.0, 1.0],
        [100.0, 0.0],
      ],
      [
        [100.2, 0.2],
        [100.8, 0.2],
        [100.8, 0.8],
        [100.2, 0.8],
        [100.2, 0.2],
      ],
    ],
  };

  var polygonWithHeights = {
    type: "Polygon",
    coordinates: [
      [
        [100.0, 0.0, 1.0],
        [101.0, 0.0, 2.0],
        [101.0, 1.0, 1.0],
        [100.0, 1.0, 2.0],
        [100.0, 0.0, 3.0],
      ],
    ],
  };

  var multiPoint = {
    type: "MultiPoint",
    coordinates: [
      [100.0, 0.0],
      [101.0, 1.0],
      [101.0, 3.0],
    ],
  };

  var multiLineString = {
    type: "MultiLineString",
    coordinates: [
      [
        [100.0, 0.0],
        [101.0, 1.0],
      ],
      [
        [102.0, 2.0],
        [103.0, 3.0],
      ],
    ],
  };

  var multiPolygon = {
    type: "MultiPolygon",
    coordinates: [
      [
        [
          [102.0, 2.0],
          [103.0, 2.0],
          [103.0, 3.0],
          [102.0, 3.0],
          [102.0, 2.0],
        ],
      ],
      [
        [
          [100.0, 0.0],
          [101.0, 0.0],
          [101.0, 1.0],
          [100.0, 1.0],
          [100.0, 0.0],
        ],
      ],
    ],
  };

  var geometryCollection = {
    type: "GeometryCollection",
    geometries: [
      {
        type: "Point",
        coordinates: [100.0, 0.0],
      },
      {
        type: "LineString",
        coordinates: [
          [101.0, 0.0],
          [102.0, 1.0],
        ],
      },
    ],
  };

  var feature = {
    type: "Feature",
    geometry: point,
  };

  var featureWithNullName = {
    type: "Feature",
    geometry: point,
    properties: {
      name: null,
    },
  };

  var featureWithId = {
    id: "myId",
    type: "Feature",
    geometry: geometryCollection,
  };

  var featureUndefinedGeometry = {
    type: "Feature",
  };

  var featureNullGeometry = {
    type: "Feature",
    geometry: null,
  };

  var unknownGeometry = {
    type: "TimeyWimey",
    coordinates: [0, 0],
  };

  var featureUnknownGeometry = {
    type: "Feature",
    geometry: unknownGeometry,
  };

  var geometryCollectionUnknownType = {
    type: "GeometryCollection",
    geometries: [unknownGeometry],
  };

  var topoJson = {
    type: "Topology",
    transform: {
      scale: [1, 1],
      translate: [0, 0],
    },
    objects: {
      polygon: {
        type: "Polygon",
        arcs: [[0, 1, 2, 3]],
        properties: {
          myProps: 0,
        },
      },
      lineString: {
        type: "LineString",
        arcs: [4],
        properties: {
          myProps: 1,
        },
      },
    },
    arcs: [
      [
        [0, 0],
        [1, 0],
        [0, 1],
        [-1, 0],
        [0, -1],
      ],
      [
        [0, 0],
        [1, 0],
        [0, 1],
      ],
      [
        [1, 1],
        [-1, 0],
        [0, -1],
      ],
      [[1, 1]],
      [[0, 0]],
    ],
  };

  var mixedGeometries = {
    type: "GeometryCollection",
    geometries: [lineString, polygon, point],
  };

  it("default constructor has expected values", function () {
    var dataSource = new GeoJsonDataSource();
    expect(dataSource.changedEvent).toBeInstanceOf(Event);
    expect(dataSource.errorEvent).toBeInstanceOf(Event);
    expect(dataSource.clock).toBeUndefined();
    expect(dataSource.name).toBeUndefined();
    expect(dataSource.entities).toBeInstanceOf(EntityCollection);
    expect(dataSource.entities.values.length).toEqual(0);
    expect(dataSource.show).toBe(true);
    expect(dataSource.credit).toBeUndefined();
  });

  it("credit gets set from options", function () {
    return GeoJsonDataSource.load(point, {
      credit: "This is my credit",
    }).then(function (dataSource) {
      expect(dataSource.credit).toBeInstanceOf(Credit);
    });
  });

  it("setting name raises changed event", function () {
    var dataSource = new GeoJsonDataSource();

    var spy = jasmine.createSpy("changedEvent");
    dataSource.changedEvent.addEventListener(spy);

    var newName = "chester";
    dataSource.name = newName;
    expect(dataSource.name).toEqual(newName);
    expect(spy.calls.count()).toEqual(1);
    expect(spy).toHaveBeenCalledWith(dataSource);
  });

  it("show sets underlying entity collection show.", function () {
    var dataSource = new GeoJsonDataSource();

    dataSource.show = false;
    expect(dataSource.show).toBe(false);
    expect(dataSource.show).toEqual(dataSource.entities.show);

    dataSource.show = true;
    expect(dataSource.show).toBe(true);
    expect(dataSource.show).toEqual(dataSource.entities.show);
  });

  it("Works with null geometry", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource.load(featureNullGeometry).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.properties).toBe(featureNullGeometry.properties);
      expect(entity.position).toBeUndefined();
    });
  });

  it("Works with feature", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource.load(feature).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.properties).toBe(feature.properties);
      expect(entity.position.getValue(time)).toEqual(
        coordinatesToCartesian(feature.geometry.coordinates)
      );
      expect(entity.billboard).toBeDefined();
    });
  });

  it("Creates default description from properties", function () {
    var featureWithProperties = {
      type: "Feature",
      geometry: point,
      properties: {
        prop1: "dog",
        prop2: "cat",
        prop3: "liger",
      },
    };

    var dataSource = new GeoJsonDataSource();
    return dataSource.load(featureWithProperties).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.description).toBeDefined();
      var description = entity.description.getValue(time);
      expect(description).toContain("prop1");
      expect(description).toContain("prop2");
      expect(description).toContain("prop3");
      expect(description).toContain("dog");
      expect(description).toContain("cat");
      expect(description).toContain("liger");
    });
  });

  it("Creates custom description string from properties", function () {
    var featureWithProperties = {
      type: "Feature",
      geometry: point,
      properties: {
        prop1: "dog",
        prop2: "cat",
      },
    };

    function testDescribe(properties) {
      var desc = "";
      for (var key in properties) {
        if (properties.hasOwnProperty(key)) {
          var value = properties[key];
          desc += key + " = " + value + ". ";
        }
      }
      return desc;
    }

    var dataSource = new GeoJsonDataSource();
    var options = {
      describe: testDescribe,
    };
    return dataSource.load(featureWithProperties, options).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.description).toBeDefined();
      var description = entity.description.getValue(time);
      expect(description).toContain("prop1 = dog.");
      expect(description).toContain("prop2 = cat.");
    });
  });

  it("Creates custom description from properties, using a describeProperty", function () {
    var featureWithProperties = {
      type: "Feature",
      geometry: point,
      properties: {
        prop1: "dog",
        prop2: "cat",
      },
    };

    function testDescribe(properties) {
      var desc = "";
      for (var key in properties) {
        if (properties.hasOwnProperty(key)) {
          var value = properties[key];
          desc += key + " = " + value + "; ";
        }
      }
      return desc;
    }
    function createDescriptionCallback(describe, properties, nameProperty) {
      var description;
      return function (time, result) {
        if (!description) {
          description = describe(properties, nameProperty);
        }
        return description;
      };
    }
    function testDescribeProperty(properties, nameProperty) {
      return new CallbackProperty(
        createDescriptionCallback(testDescribe, properties, nameProperty),
        true
      );
    }

    var dataSource = new GeoJsonDataSource();
    var options = {
      describe: testDescribeProperty,
    };
    return dataSource.load(featureWithProperties, options).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.description).toBeDefined();
      var description = entity.description.getValue(time);
      expect(description).toContain("prop1 = dog;");
      expect(description).toContain("prop2 = cat;");
    });
  });

  it("Uses description if present", function () {
    var featureWithDescription = {
      type: "Feature",
      geometry: point,
      properties: {
        prop1: "dog",
        prop2: "cat",
        prop3: "liger",
        description: "This is my descriptiong!",
      },
    };

    var dataSource = new GeoJsonDataSource();
    return dataSource.load(featureWithDescription).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.description).toBeDefined();
      expect(entity.description.getValue(time)).toEqual(
        featureWithDescription.properties.description
      );
    });
  });

  it("Handles null description", function () {
    var featureWithNullDescription = {
      type: "Feature",
      geometry: point,
      properties: {
        description: null,
      },
    };

    var dataSource = new GeoJsonDataSource();
    return dataSource.load(featureWithNullDescription).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.description).toBeUndefined();
    });
  });

  it('Does not use "name" property as the object\'s name if it is null', function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource.load(featureWithNullName).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.name).toBeUndefined();
      expect(entity.properties.name.getValue()).toBe(
        featureWithNullName.properties.name
      );
      expect(entity.properties.getValue(time)).toEqual(
        featureWithNullName.properties
      );
      expect(entity.position.getValue(time)).toEqual(
        coordinatesToCartesian(featureWithNullName.geometry.coordinates)
      );
      expect(entity.billboard).toBeDefined();
    });
  });

  it("Works with feature with id", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource.load(featureWithId).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.id).toEqual(featureWithId.id);
      entity = entityCollection.values[1];
      expect(entity.id).toEqual(featureWithId.id + "_2");
    });
  });

  it("Works with null id", function () {
    var geojson = {
      id: null,
      type: "Feature",
      geometry: null,
    };

    var dataSource = new GeoJsonDataSource();
    return dataSource.load(geojson).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.id).not.toEqual(null);
    });
  });

  it("Works with null properties", function () {
    var geojson = {
      type: "Feature",
      geometry: null,
      properties: null,
    };

    var dataSource = new GeoJsonDataSource();
    return dataSource.load(geojson).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.properties).toBeUndefined();
    });
  });

  it("Has entity collection with link to data source", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource.load(featureWithId).then(function () {
      var entityCollection = dataSource.entities;
      expect(entityCollection.owner).toEqual(dataSource);
    });
  });

  it("Has entity with link to entity collection", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource.load(featureWithId).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.entityCollection).toEqual(entityCollection);
    });
  });

  it("Works with point geometry", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource.load(point).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.properties).toBe(point.properties);
      expect(entity.position.getValue(time)).toEqual(
        coordinatesToCartesian(point.coordinates)
      );
      expect(entity.billboard).toBeDefined();
      expect(entity.billboard.image).toBeDefined();
    });
  });

  it("Works with point geometry clamped to ground", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource
      .load(point, {
        clampToGround: true,
      })
      .then(function () {
        var entityCollection = dataSource.entities;
        var entity = entityCollection.values[0];
        expect(entity.properties).toBe(point.properties);
        expect(entity.position.getValue(time)).toEqual(
          coordinatesToCartesian(point.coordinates)
        );
        expect(entity.billboard).toBeDefined();
        expect(entity.billboard.image).toBeDefined();
        expect(entity.billboard.heightReference.getValue(time)).toBe(
          HeightReference.CLAMP_TO_GROUND
        );
      });
  });

  it("Works with point geometry with simplystyle", function () {
    var geojson = {
      type: "Point",
      coordinates: [102.0, 0.5],
      properties: {
        "marker-size": "large",
        "marker-symbol": "bus",
        "marker-color": "#ffffff",
      },
    };

    var dataSource = new GeoJsonDataSource();
    return dataSource.load(geojson).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.billboard).toBeDefined();
      return when(
        dataSource._pinBuilder.fromMakiIconId("bus", Color.WHITE, 64)
      ).then(function (image) {
        expect(entity.billboard.image.getValue()).toBe(image);
      });
    });
  });

  it("Works with point geometry with null simplystyle", function () {
    var geojson = {
      type: "Point",
      coordinates: [102.0, 0.5],
      properties: {
        "marker-size": null,
        "marker-symbol": null,
        "marker-color": null,
      },
    };

    var dataSource = new GeoJsonDataSource();
    return dataSource.load(geojson).then(function () {
      var image = dataSource._pinBuilder.fromColor(
        GeoJsonDataSource.markerColor,
        GeoJsonDataSource.markerSize
      );
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.billboard).toBeDefined();
      expect(entity.billboard.image.getValue()).toBe(image);
    });
  });

  it("Works with point geometry and unknown simplystyle", function () {
    var geojson = {
      type: "Point",
      coordinates: [102.0, 0.5],
      properties: {
        "marker-size": "large",
        "marker-symbol": "notAnIcon",
        "marker-color": "#ffffff",
      },
    };

    var dataSource = new GeoJsonDataSource();
    return dataSource.load(geojson).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.billboard).toBeDefined();
      return when(dataSource._pinBuilder.fromColor(Color.WHITE, 64)).then(
        function (image) {
          expect(entity.billboard.image.getValue()).toBe(image);
        }
      );
    });
  });

  it("Works with multipoint geometry", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource.load(multiPoint).then(function () {
      var entityCollection = dataSource.entities;
      var entities = entityCollection.values;
      var expectedPositions = coordinatesArrayToCartesian(
        multiPoint.coordinates
      );
      for (var i = 0; i < multiPoint.coordinates.length; i++) {
        var entity = entities[i];
        expect(entity.properties).toBe(multiPoint.properties);
        expect(entity.position.getValue(time)).toEqual(expectedPositions[i]);
        expect(entity.billboard).toBeDefined();
        expect(entity.billboard.image).toBeDefined();
      }
    });
  });

  it("Works with multipoint geometry clamped to ground", function () {
    GeoJsonDataSource.clampToGround = true;
    var dataSource = new GeoJsonDataSource();
    return dataSource.load(multiPoint).then(function () {
      var entityCollection = dataSource.entities;
      var entities = entityCollection.values;
      var expectedPositions = coordinatesArrayToCartesian(
        multiPoint.coordinates
      );
      for (var i = 0; i < multiPoint.coordinates.length; i++) {
        var entity = entities[i];
        expect(entity.properties).toBe(multiPoint.properties);
        expect(entity.position.getValue(time)).toEqual(expectedPositions[i]);
        expect(entity.billboard).toBeDefined();
        expect(entity.billboard.image).toBeDefined();
        expect(entity.billboard.heightReference.getValue()).toBe(
          HeightReference.CLAMP_TO_GROUND
        );
      }
    });
  });

  it("Works with lineString geometry", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource.load(lineString).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.properties).toBe(lineString.properties);
      expect(entity.polyline.positions.getValue(time)).toEqual(
        coordinatesArrayToCartesian(lineString.coordinates)
      );
      expect(entity.polyline.material.color.getValue(time)).toEqual(
        GeoJsonDataSource.stroke
      );
      expect(entity.polyline.width.getValue(time)).toEqual(2);
    });
  });

  it("Works with lineString geometry clamped to ground", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource
      .load(lineString, {
        clampToGround: true,
      })
      .then(function () {
        var entityCollection = dataSource.entities;
        var entity = entityCollection.values[0];
        expect(entity.properties).toBe(lineString.properties);
        expect(entity.polyline.positions.getValue(time)).toEqual(
          coordinatesArrayToCartesian(lineString.coordinates)
        );
        expect(entity.polyline.material.color.getValue(time)).toEqual(
          GeoJsonDataSource.stroke
        );
        expect(entity.polyline.width.getValue(time)).toEqual(2);
        expect(entity.polyline.clampToGround.getValue(time)).toEqual(true);
      });
  });

  it("Works with multiLineString geometry", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource.load(multiLineString).then(function () {
      var entityCollection = dataSource.entities;
      var entities = entityCollection.values;
      var lines = multiLineToCartesian(multiLineString);
      for (var i = 0; i < multiLineString.coordinates.length; i++) {
        var entity = entities[i];
        expect(entity.properties).toBe(multiLineString.properties);
        expect(entity.polyline.positions.getValue(time)).toEqual(lines[i]);
        expect(entity.polyline.material.color.getValue(time)).toEqual(
          Color.YELLOW
        );
        expect(entity.polyline.width.getValue(time)).toEqual(2);
      }
    });
  });

  it("Works with multiLineString geometry clamped to ground", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource
      .load(multiLineString, {
        clampToGround: true,
      })
      .then(function () {
        var entityCollection = dataSource.entities;
        var entities = entityCollection.values;
        var lines = multiLineToCartesian(multiLineString);
        for (var i = 0; i < multiLineString.coordinates.length; i++) {
          var entity = entities[i];
          expect(entity.properties).toBe(multiLineString.properties);
          expect(entity.polyline.positions.getValue(time)).toEqual(lines[i]);
          expect(entity.polyline.material.color.getValue(time)).toEqual(
            Color.YELLOW
          );
          expect(entity.polyline.width.getValue(time)).toEqual(2);
          expect(entity.polyline.clampToGround.getValue(time)).toEqual(true);
        }
      });
  });

  it("Works with polygon geometry", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource.load(polygon).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.properties).toBe(polygon.properties);
      expect(entity.polygon.hierarchy.getValue(time)).toEqual(
        new PolygonHierarchy(
          polygonCoordinatesToCartesian(polygon.coordinates[0])
        )
      );
      expect(entity.polygon.perPositionHeight).toBeUndefined();
      expect(entity.polygon.material.color.getValue(time)).toEqual(
        GeoJsonDataSource.fill
      );
      expect(entity.polygon.outline.getValue(time)).toEqual(true);
      expect(entity.polygon.outlineWidth.getValue(time)).toEqual(
        GeoJsonDataSource.strokeWidth
      );
      expect(entity.polygon.outlineColor.getValue(time)).toEqual(
        GeoJsonDataSource.stroke
      );
      expect(entity.polygon.height).toBeInstanceOf(ConstantProperty);
    });
  });

  it("Works with polygon geometry clamped to ground", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource
      .load(polygon, {
        clampToGround: true,
      })
      .then(function () {
        var entityCollection = dataSource.entities;
        var entity = entityCollection.values[0];
        expect(entity.properties).toBe(polygon.properties);
        expect(entity.polygon.hierarchy.getValue(time)).toEqual(
          new PolygonHierarchy(
            polygonCoordinatesToCartesian(polygon.coordinates[0])
          )
        );
        expect(entity.polygon.perPositionHeight).toBeUndefined();
        expect(entity.polygon.material.color.getValue(time)).toEqual(
          GeoJsonDataSource.fill
        );
        expect(entity.polygon.outline.getValue(time)).toEqual(true);
        expect(entity.polygon.outlineWidth.getValue(time)).toEqual(
          GeoJsonDataSource.strokeWidth
        );
        expect(entity.polygon.outlineColor.getValue(time)).toEqual(
          GeoJsonDataSource.stroke
        );
        expect(entity.polygon.height).toBeUndefined();
      });
  });

  it("Works with polygon geometry with Heights", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource.load(polygonWithHeights).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.properties).toBe(polygonWithHeights.properties);
      expect(entity.polygon.hierarchy.getValue(time)).toEqual(
        new PolygonHierarchy(
          polygonCoordinatesToCartesian(polygonWithHeights.coordinates[0])
        )
      );
      expect(entity.polygon.perPositionHeight.getValue(time)).toBe(true);
      expect(entity.polygon.material.color.getValue(time)).toEqual(
        GeoJsonDataSource.fill
      );
      expect(entity.polygon.outline.getValue(time)).toEqual(true);
      expect(entity.polygon.outlineWidth.getValue(time)).toEqual(
        GeoJsonDataSource.strokeWidth
      );
      expect(entity.polygon.outlineColor.getValue(time)).toEqual(
        GeoJsonDataSource.stroke
      );
    });
  });

  it("Works with polygon geometry with holes", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource.load(polygonWithHoles).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.properties).toBe(polygonWithHoles.properties);
      expect(entity.polygon.hierarchy.getValue(time)).toEqual(
        new PolygonHierarchy(
          polygonCoordinatesToCartesian(polygonWithHoles.coordinates[0]),
          [
            new PolygonHierarchy(
              polygonCoordinatesToCartesian(polygonWithHoles.coordinates[1])
            ),
          ]
        )
      );
    });
  });

  it("Works with multiPolygon geometry", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource.load(multiPolygon).then(function () {
      var entityCollection = dataSource.entities;
      var entities = entityCollection.values;
      var positions = multiPolygonCoordinatesToCartesian(
        multiPolygon.coordinates
      );
      for (var i = 0; i < multiPolygon.coordinates.length; i++) {
        var entity = entities[i];
        expect(entity.properties).toBe(multiPolygon.properties);
        expect(entity.polygon.hierarchy.getValue(time)).toEqual(
          new PolygonHierarchy(positions[i])
        );
      }
    });
  });

  it("Works with topojson geometry", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource.load(topoJson).then(function () {
      var entityCollection = dataSource.entities;
      var entities = entityCollection.values;

      var polygon = entities[0];
      expect(polygon.properties.myProps.getValue()).toBe(
        topoJson.objects.polygon.properties.myProps
      );
      expect(polygon.properties.getValue(time)).toEqual(
        topoJson.objects.polygon.properties
      );

      expect(polygon.polygon.hierarchy).toBeDefined();

      var lineString = entities[1];
      expect(lineString.properties.myProps.getValue()).toBe(
        topoJson.objects.lineString.properties.myProps
      );
      expect(lineString.properties.getValue(time)).toEqual(
        topoJson.objects.lineString.properties
      );

      expect(lineString.polyline).toBeDefined();
    });
  });

  it("Can provide base styling options", function () {
    var options = {
      markerSize: 10,
      markerSymbol: "bus",
      markerColor: Color.GREEN,
      stroke: Color.ORANGE,
      strokeWidth: 8,
      fill: Color.RED,
    };

    var dataSource = new GeoJsonDataSource();
    return dataSource.load(mixedGeometries, options).then(function () {
      var entityCollection = dataSource.entities;
      var entities = entityCollection.values;

      var entity = entities[0];
      expect(entity.polyline.material.color.getValue()).toEqual(options.stroke);
      expect(entity.polyline.width.getValue()).toEqual(options.strokeWidth);

      entity = entities[1];
      expect(entity.polygon.material.color.getValue()).toEqual(options.fill);
      expect(entity.polygon.outlineColor.getValue()).toEqual(options.stroke);
      expect(entity.polygon.outlineWidth.getValue()).toEqual(
        options.strokeWidth
      );

      entity = entities[2];
      var expectedImage = dataSource._pinBuilder.fromMakiIconId(
        options.markerSymbol,
        options.markerColor,
        options.markerSize
      );
      expect(entity.billboard.image.getValue()).toEqual(expectedImage);
    });
  });

  it("Can set default graphics", function () {
    GeoJsonDataSource.markerSize = 10;
    GeoJsonDataSource.markerSymbol = "bus";
    GeoJsonDataSource.markerColor = Color.GREEN;
    GeoJsonDataSource.stroke = Color.ORANGE;
    GeoJsonDataSource.strokeWidth = 8;
    GeoJsonDataSource.fill = Color.RED;

    var dataSource = new GeoJsonDataSource();
    return dataSource.load(mixedGeometries).then(function () {
      var entityCollection = dataSource.entities;
      var entities = entityCollection.values;

      var entity = entities[0];
      expect(entity.polyline.material.color.getValue()).toEqual(
        GeoJsonDataSource.stroke
      );
      expect(entity.polyline.width.getValue()).toEqual(
        GeoJsonDataSource.strokeWidth
      );

      entity = entities[1];
      expect(entity.polygon.material.color.getValue()).toEqual(
        GeoJsonDataSource.fill
      );
      expect(entity.polygon.outlineColor.getValue()).toEqual(
        GeoJsonDataSource.stroke
      );
      expect(entity.polygon.outlineWidth.getValue()).toEqual(
        GeoJsonDataSource.strokeWidth
      );

      entity = entities[2];
      var expectedImage = dataSource._pinBuilder.fromMakiIconId(
        GeoJsonDataSource.markerSymbol,
        GeoJsonDataSource.markerColor,
        GeoJsonDataSource.markerSize
      );
      expect(entity.billboard.image.getValue()).toEqual(expectedImage);
    });
  });

  it("Generates description", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource.load(topoJson).then(function () {
      var entityCollection = dataSource.entities;
      var entities = entityCollection.values;
      var polygon = entities[0];
      expect(polygon.description).toBeDefined();
    });
  });

  it("Works with geometrycollection", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource.load(geometryCollection).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.properties).toBe(geometryCollection.properties);
      expect(entity.position.getValue(time)).toEqual(
        coordinatesToCartesian(geometryCollection.geometries[0].coordinates)
      );
      expect(entity.billboard).toBeDefined();

      entity = entityCollection.values[1];
      expect(entity.properties).toBe(geometryCollection.properties);
      expect(entity.polyline.positions.getValue(time)).toEqual(
        coordinatesArrayToCartesian(
          geometryCollection.geometries[1].coordinates
        )
      );
    });
  });

  it("Works with named crs", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource.load(pointNamedCrs).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.position.getValue(time)).toEqual(
        coordinatesToCartesian(point.coordinates)
      );
    });
  });

  it("Works with named crs OGC:1.3:CRS84", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource.load(pointNamedCrsOgc).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.position.getValue(time)).toEqual(
        coordinatesToCartesian(point.coordinates)
      );
    });
  });

  it("Works with named crs EPSG::4326", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource.load(pointNamedCrsEpsg).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.position.getValue(time)).toEqual(
        coordinatesToCartesian(point.coordinates)
      );
    });
  });

  it("Works with link crs href", function () {
    var projectedPosition = new Cartesian3(1, 2, 3);
    GeoJsonDataSource.crsLinkHrefs[
      pointCrsLinkHref.crs.properties.href
    ] = function (properties) {
      expect(properties).toBe(pointCrsLinkHref.crs.properties);
      return when(properties.href).then(function (href) {
        return function (coordinate) {
          expect(coordinate).toBe(pointCrsLinkHref.coordinates);
          return projectedPosition;
        };
      });
    };

    var dataSource = new GeoJsonDataSource();
    return dataSource.load(pointCrsLinkHref).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.position.getValue(time)).toEqual(projectedPosition);
    });
  });

  it("Works with EPSG crs", function () {
    var dataSource = new GeoJsonDataSource();

    return dataSource.load(pointCrsEpsg).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.position.getValue(time)).toEqual(
        coordinatesToCartesian(point.coordinates)
      );
    });
  });

  it("Works with polyline using simplestyle", function () {
    var geoJson = {
      type: "Feature",
      geometry: {
        type: "LineString",
        coordinates: [
          [100.0, 0.0],
          [101.0, 1.0],
        ],
      },
      properties: {
        title: "textMarker",
        description: "My description",
        stroke: "#aabbcc",
        "stroke-opacity": 0.5,
        "stroke-width": 5,
      },
    };

    var dataSource = new GeoJsonDataSource();
    return dataSource.load(geoJson).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.name).toEqual(geoJson.properties.title);
      expect(entity.description.getValue(time)).toEqual(
        geoJson.properties.description
      );

      var expectedColor = Color.fromCssColorString(geoJson.properties.stroke);
      expectedColor.alpha = geoJson.properties["stroke-opacity"];
      expect(entity.polyline.material.color.getValue(time)).toEqual(
        expectedColor
      );
      expect(entity.polyline.width.getValue(time)).toEqual(
        geoJson.properties["stroke-width"]
      );
    });
  });

  it("Works with polyline using null simplestyle values", function () {
    var geoJson = {
      type: "Feature",
      geometry: {
        type: "LineString",
        coordinates: [
          [100.0, 0.0],
          [101.0, 1.0],
        ],
      },
      properties: {
        title: null,
        description: null,
        stroke: null,
        "stroke-opacity": null,
        "stroke-width": null,
      },
    };

    var dataSource = new GeoJsonDataSource();
    return dataSource.load(geoJson).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.name).toBeUndefined();
      expect(entity.description).toBeUndefined();
      expect(entity.polyline.material.color.getValue(time)).toEqual(
        GeoJsonDataSource.stroke
      );
      expect(entity.polyline.width.getValue(time)).toEqual(
        GeoJsonDataSource.strokeWidth
      );
    });
  });

  it("Works with polygon using simplestyle", function () {
    var geoJson = {
      type: "Feature",
      geometry: {
        type: "Polygon",
        coordinates: [
          [
            [100.0, 0.0],
            [101.0, 0.0],
            [101.0, 1.0],
            [100.0, 1.0],
            [100.0, 0.0],
          ],
        ],
      },
      properties: {
        title: "textMarker",
        description: "My description",
        stroke: "#aabbcc",
        "stroke-opacity": 0.5,
        "stroke-width": 5,
        fill: "#ccaabb",
        "fill-opacity": 0.25,
      },
    };

    var dataSource = new GeoJsonDataSource();
    return dataSource.load(geoJson).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.name).toEqual(geoJson.properties.title);
      expect(entity.description.getValue(time)).toEqual(
        geoJson.properties.description
      );

      var expectedFill = Color.fromCssColorString(geoJson.properties.fill);
      expectedFill.alpha = geoJson.properties["fill-opacity"];

      var expectedOutlineColor = Color.fromCssColorString(
        geoJson.properties.stroke
      );
      expectedOutlineColor.alpha = geoJson.properties["stroke-opacity"];

      expect(entity.polygon.material.color.getValue(time)).toEqual(
        expectedFill
      );
      expect(entity.polygon.outline.getValue(time)).toEqual(true);
      expect(entity.polygon.outlineWidth.getValue(time)).toEqual(5);
      expect(entity.polygon.outlineColor.getValue(time)).toEqual(
        expectedOutlineColor
      );
    });
  });

  it("Works with polygon using null simplestyle", function () {
    var geoJson = {
      type: "Feature",
      geometry: {
        type: "Polygon",
        coordinates: [
          [
            [100.0, 0.0],
            [101.0, 0.0],
            [101.0, 1.0],
            [100.0, 1.0],
            [100.0, 0.0],
          ],
        ],
      },
      properties: {
        title: null,
        description: null,
        stroke: null,
        "stroke-opacity": null,
        "stroke-width": null,
        fill: null,
        "fill-opacity": null,
      },
    };

    var dataSource = new GeoJsonDataSource();
    return dataSource.load(geoJson).then(function () {
      var entityCollection = dataSource.entities;
      var entity = entityCollection.values[0];
      expect(entity.name).toBeUndefined();
      expect(entity.description).toBeUndefined();
      expect(entity.polygon.material.color.getValue(time)).toEqual(
        GeoJsonDataSource.fill
      );
      expect(entity.polygon.outline.getValue(time)).toEqual(true);
      expect(entity.polygon.outlineWidth.getValue(time)).toEqual(
        GeoJsonDataSource.strokeWidth
      );
      expect(entity.polygon.outlineColor.getValue(time)).toEqual(
        GeoJsonDataSource.stroke
      );
    });
  });

  it("load works with a URL", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource.load("Data/test.geojson").then(function () {
      expect(dataSource.name).toEqual("test.geojson");
    });
  });

  it("Fails when encountering unknown geometry", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource
      .load(featureUnknownGeometry)
      .then(function () {
        fail("should not be called");
      })
      .otherwise(function () {});
  });

  it("Fails with undefined geomeetry", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource
      .load(featureUndefinedGeometry)
      .then(function () {
        fail("should not be called");
      })
      .otherwise(function () {});
  });

  it("Fails with unknown geomeetry in geometryCollection", function () {
    var dataSource = new GeoJsonDataSource();
    return dataSource
      .load(geometryCollectionUnknownType)
      .then(function () {
        fail("should not be called");
      })
      .otherwise(function () {});
  });

  it("load throws with undefined geoJson", function () {
    var dataSource = new GeoJsonDataSource();
    expect(function () {
      dataSource.load(undefined);
    }).toThrowDeveloperError();
  });

  it("rejects unknown geometry", function () {
    return GeoJsonDataSource.load(unknownGeometry)
      .then(function () {
        fail("should not be called");
      })
      .otherwise(function (error) {
        expect(error).toBeInstanceOf(RuntimeError);
        expect(error.message).toContain(
          "Unsupported GeoJSON object type: TimeyWimey"
        );
      });
  });

  it("rejects invalid url", function () {
    return GeoJsonDataSource.load("invalid.geojson")
      .then(function () {
        fail("should not be called");
      })
      .otherwise(function (error) {
        expect(error.statusCode).toBe(404);
      });
  });

  it("rejects null CRS", function () {
    var featureWithNullCrs = {
      type: "Feature",
      geometry: point,
      crs: null,
    };

    return GeoJsonDataSource.load(featureWithNullCrs).then(function (
      dataSource
    ) {
      expect(dataSource.entities.values.length).toBe(0);
    });
  });

  it("rejects unknown CRS", function () {
    var featureWithUnknownCrsType = {
      type: "Feature",
      geometry: point,
      crs: {
        type: "potato",
        properties: {},
      },
    };

    return GeoJsonDataSource.load(featureWithUnknownCrsType)
      .then(function () {
        fail("should not be called");
      })
      .otherwise(function (error) {
        expect(error).toBeInstanceOf(RuntimeError);
        expect(error.message).toContain("Unknown crs type: potato");
      });
  });

  it("rejects undefined CRS properties", function () {
    var featureWithUndefinedCrsProperty = {
      type: "Feature",
      geometry: point,
      crs: {
        type: "name",
      },
    };

    return GeoJsonDataSource.load(featureWithUndefinedCrsProperty)
      .then(function () {
        fail("should not be called");
      })
      .otherwise(function (error) {
        expect(error).toBeInstanceOf(RuntimeError);
        expect(error.message).toContain("crs.properties is undefined.");
      });
  });

  it("rejects unknown CRS name", function () {
    var featureWithUnknownCrsType = {
      type: "Feature",
      geometry: point,
      crs: {
        type: "name",
        properties: {
          name: "failMe",
        },
      },
    };

    return GeoJsonDataSource.load(featureWithUnknownCrsType)
      .then(function () {
        fail("should not be called");
      })
      .otherwise(function (error) {
        expect(error).toBeInstanceOf(RuntimeError);
        expect(error.message).toContain("Unknown crs name: failMe");
      });
  });

  it("rejects unknown CRS link", function () {
    var featureWithUnknownCrsType = {
      type: "Feature",
      geometry: point,
      crs: {
        type: "link",
        properties: {
          href: "failMe",
          type: "failMeTwice",
        },
      },
    };

    return GeoJsonDataSource.load(featureWithUnknownCrsType)
      .then(function () {
        fail("should not be called");
      })
      .otherwise(function (error) {
        expect(error).toBeInstanceOf(RuntimeError);
        expect(error.message).toContain(
          'Unable to resolve crs link: {"href":"failMe","type":"failMeTwice"}'
        );
      });
  });

  it("load rejects loading non json file", function () {
    var dataSource = new GeoJsonDataSource();
    var spy = jasmine.createSpy("errorEvent");
    dataSource.errorEvent.addEventListener(spy);

    // Blue.png is not JSON
    return dataSource
      .load("Data/Images/Blue.png")
      .then(function () {
        fail("should not be called");
      })
      .otherwise(function () {
        expect(spy).toHaveBeenCalledWith(dataSource, jasmine.any(Error));
      });
  });

  it("load raises loading event", function () {
    var dataSource = new GeoJsonDataSource();
    var spy = jasmine.createSpy("loadingEvent");
    dataSource.loadingEvent.addEventListener(spy);

    var promise = dataSource.load("Data/test.geojson");
    expect(spy).toHaveBeenCalledWith(dataSource, true);
    expect(dataSource.isLoading).toBe(true);

    return promise.then(function () {
      expect(spy).toHaveBeenCalledWith(dataSource, false);
      expect(dataSource.isLoading).toBe(false);
    });
  });
});