#[cfg(not(tarpaulin_include))]
#[cfg(test)]
mod index {

    extern crate pretty_assertions;

    use core::f64::consts::PI;
    use core::fmt::Debug;

    use approx::AbsDiffEq;
    use geo::line_string;
    use geo::CoordFloat;
    use geo::Geometry;
    use geo::GeometryCollection;
    use geo::LineString;
    use geo::MultiPoint;
    use geo::Point;
    use geo::Polygon;
    use geo_types::Coord;
    use num_traits::FloatConst;
    use pretty_assertions::assert_eq;

    use crate::path::builder::Builder as PathBuilder;
    use crate::path::endpoint::Endpoint;
    use crate::path_test_context::Path2d;
    use crate::projection::equirectangular::Equirectangular;
    use crate::projection::orthographic::Orthographic;
    use crate::projection::projector_common::types::ProjectorAntimeridianResampleNoneNoClip;
    use crate::projection::Build;
    use crate::projection::PrecisionBypass;
    use crate::projection::RawBase;
    use crate::projection::RotateSet;
    use crate::projection::ScaleSet;
    use crate::stream::Stream;
    use crate::stream::Streamable;

    #[inline]
    fn equirectangular<
        EP: Clone + Stream<EP = EP, T = T> + Debug + Default,
        T: 'static + AbsDiffEq<Epsilon = T> + CoordFloat + Default + FloatConst,
    >() -> ProjectorAntimeridianResampleNoneNoClip<EP, Equirectangular<T>, T> {
        let mut e = Equirectangular::builder();
        e.scale_set(T::from(900f64 / PI).unwrap());
        e.precision_bypass().build()
    }

    fn path(
        projection: ProjectorAntimeridianResampleNoneNoClip<Endpoint, Equirectangular<f64>, f64>,
        object: &impl Streamable<T = f64>,
    ) -> Vec<String> {
        let path2d = Path2d::new().unwrap();

        let context = Endpoint::new(path2d);
        let pb = PathBuilder::new(context);

        pb.build(projection).object(object).result()
    }

    // tape("geoPath.projection() defaults to null", function(test) {
    //   var path = d3_geo.geoPath();
    //   test.strictEqual(path.projection(), null);
    //   test.end();
    // });

    // tape("geoPath.context() defaults to null", function(test) {
    //   var path = d3_geo.geoPath();
    //   test.strictEqual(path.context(), null);
    //   test.end();
    // });

    // tape("d3.geoPath(projection) sets the initial projection", function(test) {
    //   var projection = d3_geo.geoAlbers(), path = d3_geo.geoPath(projection);
    //   test.strictEqual(path.projection(), projection);
    //   test.end();
    // });

    // tape("d3.geoPath(projection, context) sets the initial projection and context", function(test) {
    //   var context = testContext(), projection = d3_geo.geoAlbers(), path = d3_geo.geoPath(projection, context);
    //   test.strictEqual(path.projection(), projection);
    //   test.strictEqual(path.context(), context);
    //   test.end();
    // });

    #[test]
    fn path_point_renders_a_point() {
        println!("geoPath(Point) renders a point");
        let object = Geometry::Point(Point::new(-63.0_f64, 18.0_f64));
        assert_eq!(
            path(equirectangular(), &object),
            [
                "type: moveTo, x: 170.0, y: 160.0",
                "type: arc, x: 165.0, y: 160.0, r: 4.5"
            ]
        );
    }

    #[test]
    fn path_point_renders_a_multipoint() {
        println!("geoPath(MultiPoint) renders a point");
        let object = Geometry::MultiPoint(MultiPoint::new(vec![
            Point::new(-63.0_f64, 18.0_f64),
            Point::new(-62.0_f64, 18.0_f64),
            Point::new(-62.0_f64, 17.0_f64),
        ]));
        assert_eq!(
            path(equirectangular(), &object),
            [
                "type: moveTo, x: 170.0, y: 160.0",
                "type: arc, x: 165.0, y: 160.0, r: 4.5",
                "type: moveTo, x: 175.0, y: 160.0",
                "type: arc, x: 170.0, y: 160.0, r: 4.5",
                "type: moveTo, x: 175.0, y: 165.0",
                "type: arc, x: 170.0, y: 165.0, r: 4.5"
            ]
        );
    }

    #[test]
    fn render_line_string() {
        println!("geoPath(LineString) renders a line string");
        let object = Geometry::LineString(line_string![
			(x: -63_f64, y: 18_f64),(x: -62_f64, y: 18_f64), (x: -62_f64, y:17_f64) ]);

        assert_eq!(
            path(equirectangular(), &object),
            [
                "type: moveTo, x: 165.0, y: 160.0",
                "type: lineTo, x: 170.0, y: 160.0",
                "type: lineTo, x: 170.0, y: 165.0"
            ]
        );
    }

    #[test]
    fn render_a_polygon() {
        println!("geoPath(Polygon) renders a polygon");
        let object = Geometry::Polygon(Polygon::new(
            LineString(vec![
                Coord {
                    x: -63_f64,
                    y: 18_f64,
                },
                Coord {
                    x: -62_f64,
                    y: 18_f64,
                },
                Coord {
                    x: -62_f64,
                    y: 17_f64,
                },
            ]),
            vec![],
        ));
        assert_eq!(
            path(equirectangular(), &object),
            [
                "type: moveTo, x: 165.0, y: 160.0",
                "type: lineTo, x: 170.0, y: 160.0",
                "type: lineTo, x: 170.0, y: 165.0",
                "closePath"
            ]
        );
    }

    #[test]
    fn render_a_gc() {
        println!("geoPath(GeometryCollection) renders a geometry collection");
        let object = Geometry::GeometryCollection(GeometryCollection(vec![Geometry::Polygon(
            Polygon::new(
                LineString(vec![
                    Coord {
                        x: -63_f64,
                        y: 18_f64,
                    },
                    Coord {
                        x: -62_f64,
                        y: 18_f64,
                    },
                    Coord {
                        x: -62_f64,
                        y: 17_f64,
                    },
                ]),
                vec![],
            ),
        )]));
        assert_eq!(
            path(equirectangular(), &object),
            [
                "type: moveTo, x: 165.0, y: 160.0",
                "type: lineTo, x: 170.0, y: 160.0",
                "type: lineTo, x: 170.0, y: 165.0",
                "closePath"
            ]
        );
    }

    //     // tape("geoPath(Feature) renders a feature", function(test) {
    //     //   test.deepEqual(testPath(equirectangular, {
    //     //     type: "Feature",
    //     //     geometry: {
    //     //       type: "Polygon",
    //     //       coordinates: [[[-63, 18], [-62, 18], [-62, 17], [-63, 18]]]
    //     //     }
    //     //   }), [
    //     //     {type: "moveTo", x: 165, y: 160},
    //     //     {type: "lineTo", x: 170, y: 160},
    //     //     {type: "lineTo", x: 170, y: 165},
    //     //     {type: "closePath"}
    //     //   ]);
    //     //   test.end();
    //     // });

    //     // tape("geoPath(FeatureCollection) renders a feature collection", function(test) {
    //     //   test.deepEqual(testPath(equirectangular, {
    //     //     type: "FeatureCollection",
    //     //     features: [{
    //     //       type: "Feature",
    //     //       geometry: {
    //     //         type: "Polygon",
    //     //         coordinates: [[[-63, 18], [-62, 18], [-62, 17], [-63, 18]]]
    //     //       }
    //     //     }]
    //     //   }), [
    //     //     {type: "moveTo", x: 165, y: 160},
    //     //     {type: "lineTo", x: 170, y: 160},
    //     //     {type: "lineTo", x: 170, y: 165},
    //     //     {type: "closePath"}
    //     //   ]);
    //     //   test.end();
    //     // });

    #[test]
    fn wrap_longitude_outside_180() {
        println!("geoPath(…) wraps longitudes outside of ±180°");
        let object = Geometry::Point(Point::new(180_f64 + 1e-6_f64, 0_f64));
        assert_eq!(
            path(equirectangular(), &object),
            [
                "type: moveTo, x: -415.0, y: 250.0",
                "type: arc, x: -420.0, y: 250.0, r: 4.5"
            ]
        );
    }

    #[test]
    #[allow(clippy::unreadable_literal)]
    fn observes_the_correct_winding_order_of_a_tiny_polygon() {
        println!("geoPath(…) observes the correct winding order of a tiny polygon");
        let object = Geometry::Polygon(Polygon::new(
            LineString(vec![
                Coord {
                    x: -0.06904102953339501,
                    y: 0.346043661846373,
                },
                Coord {
                    x: -6.725674252975136e-15,
                    y: 0.3981303360336475,
                },
                Coord {
                    x: -6.742247658534323e-15,
                    y: -0.08812465346531581,
                },
                Coord {
                    x: -0.17301258217724075,
                    y: -0.12278150669440671,
                },
                Coord {
                    x: -0.06904102953339501,
                    y: 0.346043661846373,
                },
            ]),
            vec![],
        ));
        assert_eq!(
            path(equirectangular(), &object),
            [
                "type: moveTo, x: 480.0, y: 248.0",
                "type: lineTo, x: 480.0, y: 248.0",
                "type: lineTo, x: 480.0, y: 250.0",
                "type: lineTo, x: 479.0, y: 251.0",
                "closePath"
            ]
        );
    }

    //     // tape("geoPath.projection(null)(…) does not transform coordinates", function(test) {
    //     //   test.deepEqual(testPath(null, {
    //     //     type: "Polygon",
    //     //     coordinates: [[[-63, 18], [-62, 18], [-62, 17], [-63, 18]]]
    //     //   }), [
    //     //     {type: "moveTo", x: -63, y: 18},
    //     //     {type: "lineTo", x: -62, y: 18},
    //     //     {type: "lineTo", x: -62, y: 17},
    //     //     {type: "closePath"}
    //     //   ]);
    //     //   test.end();
    //     // });

    //     // tape("geoPath.context(null)(null) returns null", function(test) {
    //     //   var path = d3_geo.geoPath();
    //     //   test.strictEqual(path(), null);
    //     //   test.strictEqual(path(null), null);
    //     //   test.strictEqual(path(undefined), null);
    //     //   test.end();
    //     // });

    //     // tape("geoPath.context(null)(Unknown) returns null", function(test) {
    //     //   var path = d3_geo.geoPath();
    //     //   test.strictEqual(path({type: "Unknown"}), null);
    //     //   test.strictEqual(path({type: "__proto__"}), null);
    //     //   test.end();
    //     // });

    #[test]
    fn does_not_treat_the_point_as_part_of_a_line() {
        println!(
            "geoPath(LineString) then geoPath(Point) does not treat the point as part of a line"
        );

        let path2d = Path2d::new().unwrap();

        let context = Endpoint::new(path2d);
        let pb = PathBuilder::new(context);

        let mut path = pb.build(equirectangular());

        let object = LineString(vec![
            Coord {
                x: -63_f64,
                y: 18_f64,
            },
            Coord {
                x: -62_f64,
                y: 18_f64,
            },
            Coord {
                x: -62_f64,
                y: 17_f64,
            },
        ]);

        assert_eq!(
            path.object(&object).result(),
            [
                "type: moveTo, x: 165.0, y: 160.0",
                "type: lineTo, x: 170.0, y: 160.0",
                "type: lineTo, x: 170.0, y: 165.0",
            ]
        );
        let object = Geometry::Point(Point::new(-63_f64, 18_f64));
        assert_eq!(
            path.object(&object).result(),
            [
                "type: moveTo, x: 170.0, y: 160.0",
                "type: arc, x: 165.0, y: 160.0, r: 4.5"
            ]
        );
    }

    #[test]
    fn does_not_treat_the_point_as_part_of_a_polygon() {
        println!(
            "geoPath(LineString) then geoPath(Point) does not treat the point as part of a line"
        );

        let path2d = Path2d::new().unwrap();

        let context = Endpoint::new(path2d);
        let pb = PathBuilder::new(context);
        let mut path = pb.build(equirectangular());

        let object = Geometry::Polygon(Polygon::new(
            LineString(vec![
                Coord {
                    x: -63_f64,
                    y: 18_f64,
                },
                Coord {
                    x: -62_f64,
                    y: 18_f64,
                },
                Coord {
                    x: -62_f64,
                    y: 17_f64,
                },
            ]),
            vec![],
        ));

        assert_eq!(
            path.object(&object).result(),
            [
                "type: moveTo, x: 165.0, y: 160.0",
                "type: lineTo, x: 170.0, y: 160.0",
                "type: lineTo, x: 170.0, y: 165.0",
                "closePath",
            ]
        );
        let object = Geometry::Point(Point::new(-63_f64, 18_f64));
        assert_eq!(
            path.object(&object).result(),
            [
                "type: moveTo, x: 170.0, y: 160.0",
                "type: arc, x: 165.0, y: 160.0, r: 4.5"
            ]
        );
    }

    /// This test is not in the javascript original.
    /// it was used to diagnose a problem in the `d3_geo_voronoi/benchmark`.
    /// After rendering polygons additional point were not drawn.
    #[test]
    fn emulate_benchmark() {
        println!("emulate benchmark");

        let path2d = Path2d::new().unwrap();

        let context = Endpoint::new(path2d);
        let pb = PathBuilder::new(context);

        let mut ob = Orthographic::builder();
        ob.rotate2_set(&[95_f64, 0_f64]);
        let ortho = ob.build();
        let mut path = pb.build(ortho);

        let object = Geometry::Polygon(Polygon::new(
            LineString(vec![
                Coord {
                    x: -63_f64,
                    y: 18_f64,
                },
                Coord {
                    x: -62_f64,
                    y: 18_f64,
                },
                Coord {
                    x: -62_f64,
                    y: 17_f64,
                },
            ]),
            vec![],
        ));

        assert_eq!(
            path.object(&object).result(),
            [
                "type: moveTo, x: 606.0, y: 173.0",
                "type: lineTo, x: 609.0, y: 173.0",
                "type: lineTo, x: 610.0, y: 177.0",
                "closePath",
            ]
        );
        let object = Geometry::Point(Point::new(-63_f64, 18_f64));
        assert_eq!(
            path.object(&object).result(),
            [
                "type: moveTo, x: 610.0, y: 173.0",
                "type: arc, x: 606.0, y: 173.0, r: 4.5",
            ]
        );
    }
}
