use geo_types::{self, CoordFloat};

use crate::geometry;

use crate::{Error, Result};
use crate::{Feature, FeatureCollection, GeoJson, LineStringType, PointType, PolygonType};
use std::convert::{TryFrom, TryInto};

#[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))]
impl<T> TryFrom<&geometry::Value> for geo_types::Point<T>
where
    T: CoordFloat,
{
    type Error = Error;

    fn try_from(value: &geometry::Value) -> Result<Self> {
        match value {
            geometry::Value::Point(point_type) => Ok(create_geo_point(point_type)),
            other => Err(mismatch_geom_err("Point", other)),
        }
    }
}
try_from_owned_value!(geo_types::Point<T>);

#[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))]
impl<T> TryFrom<&geometry::Value> for geo_types::MultiPoint<T>
where
    T: CoordFloat,
{
    type Error = Error;

    fn try_from(value: &geometry::Value) -> Result<Self> {
        match value {
            geometry::Value::MultiPoint(multi_point_type) => Ok(geo_types::MultiPoint(
                multi_point_type
                    .iter()
                    .map(|point_type| create_geo_point(point_type))
                    .collect(),
            )),
            other => Err(mismatch_geom_err("MultiPoint", other)),
        }
    }
}
try_from_owned_value!(geo_types::MultiPoint<T>);

#[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))]
impl<T> TryFrom<&geometry::Value> for geo_types::LineString<T>
where
    T: CoordFloat,
{
    type Error = Error;

    fn try_from(value: &geometry::Value) -> Result<Self> {
        match value {
            geometry::Value::LineString(multi_point_type) => {
                Ok(create_geo_line_string(multi_point_type))
            }
            other => Err(mismatch_geom_err("LineString", other)),
        }
    }
}
try_from_owned_value!(geo_types::LineString<T>);

#[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))]
impl<T> TryFrom<&geometry::Value> for geo_types::MultiLineString<T>
where
    T: CoordFloat,
{
    type Error = Error;

    fn try_from(value: &geometry::Value) -> Result<Self> {
        match value {
            geometry::Value::MultiLineString(multi_line_string_type) => {
                Ok(create_geo_multi_line_string(multi_line_string_type))
            }
            other => Err(mismatch_geom_err("MultiLineString", other)),
        }
    }
}
try_from_owned_value!(geo_types::MultiLineString<T>);

#[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))]
impl<T> TryFrom<&geometry::Value> for geo_types::Polygon<T>
where
    T: CoordFloat,
{
    type Error = Error;

    fn try_from(value: &geometry::Value) -> Result<Self> {
        match value {
            geometry::Value::Polygon(polygon_type) => Ok(create_geo_polygon(polygon_type)),
            other => Err(mismatch_geom_err("Polygon", other)),
        }
    }
}
try_from_owned_value!(geo_types::Polygon<T>);

#[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))]
impl<T> TryFrom<&geometry::Value> for geo_types::MultiPolygon<T>
where
    T: CoordFloat,
{
    type Error = Error;

    fn try_from(value: &geometry::Value) -> Result<geo_types::MultiPolygon<T>> {
        match value {
            geometry::Value::MultiPolygon(multi_polygon_type) => {
                Ok(create_geo_multi_polygon(multi_polygon_type))
            }
            other => Err(mismatch_geom_err("MultiPolygon", other)),
        }
    }
}
try_from_owned_value!(geo_types::MultiPolygon<T>);

#[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))]
impl<T> TryFrom<&geometry::Value> for geo_types::GeometryCollection<T>
where
    T: CoordFloat,
{
    type Error = Error;

    fn try_from(value: &geometry::Value) -> Result<Self> {
        match value {
            geometry::Value::GeometryCollection(geometries) => {
                let geojson_geometries = geometries
                    .iter()
                    .map(|geometry| (&geometry.value).try_into().unwrap())
                    .collect();

                Ok(geo_types::GeometryCollection(geojson_geometries))
            }
            other => Err(mismatch_geom_err("GeometryCollection", other)),
        }
    }
}
try_from_owned_value!(geo_types::GeometryCollection<T>);

#[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))]
impl<T> TryFrom<&geometry::Value> for geo_types::Geometry<T>
where
    T: CoordFloat,
{
    type Error = Error;

    fn try_from(value: &geometry::Value) -> Result<Self> {
        match value {
            geometry::Value::Point(ref point_type) => {
                Ok(geo_types::Geometry::Point(create_geo_point(point_type)))
            }
            geometry::Value::MultiPoint(ref multi_point_type) => {
                Ok(geo_types::Geometry::MultiPoint(geo_types::MultiPoint(
                    multi_point_type
                        .iter()
                        .map(|point_type| create_geo_point(point_type))
                        .collect(),
                )))
            }
            geometry::Value::LineString(ref line_string_type) => Ok(
                geo_types::Geometry::LineString(create_geo_line_string(line_string_type)),
            ),
            geometry::Value::MultiLineString(ref multi_line_string_type) => {
                Ok(geo_types::Geometry::MultiLineString(
                    create_geo_multi_line_string(multi_line_string_type),
                ))
            }
            geometry::Value::Polygon(ref polygon_type) => Ok(geo_types::Geometry::Polygon(
                create_geo_polygon(polygon_type),
            )),
            geometry::Value::MultiPolygon(ref multi_polygon_type) => Ok(
                geo_types::Geometry::MultiPolygon(create_geo_multi_polygon(multi_polygon_type)),
            ),
            geometry::Value::GeometryCollection(ref gc_type) => {
                let gc = geo_types::Geometry::GeometryCollection(geo_types::GeometryCollection(
                    gc_type
                        .iter()
                        .cloned()
                        .map(|geom| geom.try_into())
                        .collect::<Result<Vec<geo_types::Geometry<T>>>>()?,
                ));
                Ok(gc)
            }
        }
    }
}
try_from_owned_value!(geo_types::Geometry<T>);

macro_rules! impl_try_from_geom_value {
    ($($kind:ident),*) => {
        $(
            #[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))]
            impl<T> TryFrom<&$crate::Geometry> for geo_types::$kind<T>
            where
                T: CoordFloat,
            {
                type Error = Error;

                fn try_from(geometry: &crate::Geometry) -> Result<Self> {
                    Self::try_from(&geometry.value)
                }
            }

            #[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))]
            impl<T> TryFrom<$crate::Geometry> for geo_types::$kind<T>
            where
                T: CoordFloat,
            {
                type Error = Error;

                fn try_from(geometry: crate::Geometry) -> Result<Self> {
                    Self::try_from(geometry.value)
                }
            }

            #[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))]
            impl<T> TryFrom<$crate::Feature> for geo_types::$kind<T>
            where
                T: CoordFloat,
            {
                type Error = Error;

                fn try_from(val: Feature) -> Result<Self> {
                    match val.geometry {
                        None => Err(Error::FeatureHasNoGeometry(val)),
                        Some(geom) => geom.try_into(),
                    }
                }
            }
        )*
    }
}

impl_try_from_geom_value![
    Point,
    LineString,
    Polygon,
    MultiPoint,
    MultiLineString,
    MultiPolygon,
    Geometry,
    GeometryCollection
];

impl<T: CoordFloat> TryFrom<&GeoJson> for geo_types::GeometryCollection<T> {
    type Error = Error;

    /// Process top-level `GeoJSON` items, returning a geo_types::GeometryCollection or an Error
    fn try_from(gj: &GeoJson) -> Result<geo_types::GeometryCollection<T>>
    where
        T: CoordFloat,
    {
        match gj {
            GeoJson::FeatureCollection(collection) => Ok(geo_types::GeometryCollection(
                collection
                    .features
                    .iter()
                    // Only pass on non-empty geometries
                    .filter_map(|feature| feature.geometry.as_ref())
                    .map(|geometry| geometry.clone().try_into())
                    .collect::<Result<_>>()?,
            )),
            GeoJson::Feature(feature) => {
                if let Some(geometry) = &feature.geometry {
                    Ok(geo_types::GeometryCollection(vec![geometry
                        .clone()
                        .try_into()?]))
                } else {
                    Ok(geo_types::GeometryCollection(vec![]))
                }
            }
            GeoJson::Geometry(geometry) => Ok(geo_types::GeometryCollection(vec![geometry
                .clone()
                .try_into()?])),
        }
    }
}

#[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))]
impl<T> TryFrom<FeatureCollection> for geo_types::Geometry<T>
where
    T: CoordFloat,
{
    type Error = Error;

    fn try_from(val: FeatureCollection) -> Result<geo_types::Geometry<T>> {
        Ok(geo_types::Geometry::GeometryCollection(
            geo_types::GeometryCollection::try_from(&GeoJson::FeatureCollection(val))?,
        ))
    }
}

#[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))]
impl<T> TryFrom<GeoJson> for geo_types::Geometry<T>
where
    T: CoordFloat,
{
    type Error = Error;

    fn try_from(val: GeoJson) -> Result<geo_types::Geometry<T>> {
        match val {
            GeoJson::Geometry(geom) => geom.try_into(),
            GeoJson::Feature(feat) => feat.try_into(),
            GeoJson::FeatureCollection(fc) => fc.try_into(),
        }
    }
}

fn create_geo_coordinate<T>(point_type: &PointType) -> geo_types::Coord<T>
where
    T: CoordFloat,
{
    geo_types::Coord {
        x: T::from(point_type[0]).unwrap(),
        y: T::from(point_type[1]).unwrap(),
    }
}

fn create_geo_point<T>(point_type: &PointType) -> geo_types::Point<T>
where
    T: CoordFloat,
{
    geo_types::Point::new(
        T::from(point_type[0]).unwrap(),
        T::from(point_type[1]).unwrap(),
    )
}

fn create_geo_line_string<T>(line_type: &LineStringType) -> geo_types::LineString<T>
where
    T: CoordFloat,
{
    geo_types::LineString(
        line_type
            .iter()
            .map(|point_type| create_geo_coordinate(point_type))
            .collect(),
    )
}

fn create_geo_multi_line_string<T>(
    multi_line_type: &[LineStringType],
) -> geo_types::MultiLineString<T>
where
    T: CoordFloat,
{
    geo_types::MultiLineString(
        multi_line_type
            .iter()
            .map(|point_type| create_geo_line_string(point_type))
            .collect(),
    )
}

fn create_geo_polygon<T>(polygon_type: &PolygonType) -> geo_types::Polygon<T>
where
    T: CoordFloat,
{
    let exterior = polygon_type
        .first()
        .map(|e| create_geo_line_string(e))
        .unwrap_or_else(|| create_geo_line_string(&vec![]));

    let interiors = if polygon_type.len() < 2 {
        vec![]
    } else {
        polygon_type[1..]
            .iter()
            .map(|line_string_type| create_geo_line_string(line_string_type))
            .collect()
    };

    geo_types::Polygon::new(exterior, interiors)
}

fn create_geo_multi_polygon<T>(multi_polygon_type: &[PolygonType]) -> geo_types::MultiPolygon<T>
where
    T: CoordFloat,
{
    geo_types::MultiPolygon(
        multi_polygon_type
            .iter()
            .map(|polygon_type| create_geo_polygon(polygon_type))
            .collect(),
    )
}

fn mismatch_geom_err(expected_type: &'static str, found: &geometry::Value) -> Error {
    Error::InvalidGeometryConversion {
        expected_type,
        found_type: found.type_name(),
    }
}

#[cfg(test)]
mod tests {
    use crate::{Geometry, Value};
    use serde_json::json;

    use std::convert::TryInto;

    #[test]
    fn geojson_point_conversion_test() {
        let coords = vec![100.0, 0.2];
        let geojson_point = Value::Point(coords.clone());
        let geo_point: geo_types::Point<f64> = geojson_point.try_into().unwrap();

        assert_almost_eq!(geo_point.x(), coords[0], 1e-6);
        assert_almost_eq!(geo_point.y(), coords[1], 1e-6);
    }

    #[test]
    fn geojson_multi_point_conversion_test() {
        let coord1 = vec![100.0, 0.2];
        let coord2 = vec![101.0, 1.0];
        let geojson_multi_point = Value::MultiPoint(vec![coord1.clone(), coord2.clone()]);
        let geo_multi_point: geo_types::MultiPoint<f64> = geojson_multi_point.try_into().unwrap();

        assert_almost_eq!(geo_multi_point.0[0].x(), coord1[0], 1e-6);
        assert_almost_eq!(geo_multi_point.0[0].y(), coord1[1], 1e-6);
        assert_almost_eq!(geo_multi_point.0[1].x(), coord2[0], 1e-6);
        assert_almost_eq!(geo_multi_point.0[1].y(), coord2[1], 1e-6);
    }

    #[test]
    fn geojson_line_string_conversion_test() {
        let coord1 = vec![100.0, 0.2];
        let coord2 = vec![101.0, 1.0];
        let geojson_line_string = Value::LineString(vec![coord1.clone(), coord2.clone()]);
        let geo_line_string: geo_types::LineString<f64> = geojson_line_string.try_into().unwrap();

        assert_almost_eq!(geo_line_string.0[0].x, coord1[0], 1e-6);
        assert_almost_eq!(geo_line_string.0[0].y, coord1[1], 1e-6);
        assert_almost_eq!(geo_line_string.0[1].x, coord2[0], 1e-6);
        assert_almost_eq!(geo_line_string.0[1].y, coord2[1], 1e-6);
    }

    #[test]
    fn geojson_multi_line_string_conversion_test() {
        let coord1 = vec![100.0, 0.2];
        let coord2 = vec![101.0, 1.0];
        let coord3 = vec![102.0, 0.8];
        let geojson_multi_line_string = Value::MultiLineString(vec![
            vec![coord1.clone(), coord2.clone()],
            vec![coord2.clone(), coord3.clone()],
        ]);
        let geo_multi_line_string: geo_types::MultiLineString<f64> =
            geojson_multi_line_string.try_into().unwrap();

        let geo_line_string1 = &geo_multi_line_string.0[0];
        assert_almost_eq!(geo_line_string1.0[0].x, coord1[0], 1e-6);
        assert_almost_eq!(geo_line_string1.0[0].y, coord1[1], 1e-6);
        assert_almost_eq!(geo_line_string1.0[1].x, coord2[0], 1e-6);
        assert_almost_eq!(geo_line_string1.0[1].y, coord2[1], 1e-6);

        let geo_line_string2 = &geo_multi_line_string.0[1];
        assert_almost_eq!(geo_line_string2.0[0].x, coord2[0], 1e-6);
        assert_almost_eq!(geo_line_string2.0[0].y, coord2[1], 1e-6);
        assert_almost_eq!(geo_line_string2.0[1].x, coord3[0], 1e-6);
        assert_almost_eq!(geo_line_string2.0[1].y, coord3[1], 1e-6);
    }

    #[test]
    fn geojson_polygon_conversion_test() {
        let coord1 = vec![100.0, 0.0];
        let coord2 = vec![101.0, 1.0];
        let coord3 = vec![101.0, 1.0];
        let coord4 = vec![104.0, 0.2];
        let coord5 = vec![100.9, 0.2];
        let coord6 = vec![100.9, 0.7];

        let geojson_multi_line_string_type1 = vec![
            vec![
                coord1.clone(),
                coord2.clone(),
                coord3.clone(),
                coord1.clone(),
            ],
            vec![
                coord4.clone(),
                coord5.clone(),
                coord6.clone(),
                coord4.clone(),
            ],
        ];
        let geojson_polygon = Value::Polygon(geojson_multi_line_string_type1);
        let geo_polygon: geo_types::Polygon<f64> = geojson_polygon.try_into().unwrap();

        let geo_line_string1 = geo_polygon.exterior();
        assert_almost_eq!(geo_line_string1.0[0].x, coord1[0], 1e-6);
        assert_almost_eq!(geo_line_string1.0[0].y, coord1[1], 1e-6);
        assert_almost_eq!(geo_line_string1.0[1].x, coord2[0], 1e-6);
        assert_almost_eq!(geo_line_string1.0[1].y, coord2[1], 1e-6);
        assert_almost_eq!(geo_line_string1.0[2].x, coord3[0], 1e-6);
        assert_almost_eq!(geo_line_string1.0[2].y, coord3[1], 1e-6);
        assert_almost_eq!(geo_line_string1.0[3].x, coord1[0], 1e-6);
        assert_almost_eq!(geo_line_string1.0[3].y, coord1[1], 1e-6);

        let geo_line_string2 = &geo_polygon.interiors()[0];
        assert_almost_eq!(geo_line_string2.0[0].x, coord4[0], 1e-6);
        assert_almost_eq!(geo_line_string2.0[0].y, coord4[1], 1e-6);
        assert_almost_eq!(geo_line_string2.0[1].x, coord5[0], 1e-6);
        assert_almost_eq!(geo_line_string2.0[1].y, coord5[1], 1e-6);
        assert_almost_eq!(geo_line_string2.0[2].x, coord6[0], 1e-6);
        assert_almost_eq!(geo_line_string2.0[2].y, coord6[1], 1e-6);
        assert_almost_eq!(geo_line_string2.0[3].x, coord4[0], 1e-6);
        assert_almost_eq!(geo_line_string2.0[3].y, coord4[1], 1e-6);
    }

    #[test]
    fn geojson_empty_polygon_conversion_test() {
        let geojson_polygon = Value::Polygon(vec![]);
        let geo_polygon: geo_types::Polygon<f64> = geojson_polygon.try_into().unwrap();

        assert!(geo_polygon.exterior().0.is_empty());
    }

    #[test]
    fn geojson_polygon_without_interiors_conversion_test() {
        let coord1 = vec![100.0, 0.0];
        let coord2 = vec![101.0, 1.0];
        let coord3 = vec![101.0, 1.0];

        let geojson_multi_line_string_type1 = vec![vec![
            coord1.clone(),
            coord2.clone(),
            coord3.clone(),
            coord1.clone(),
        ]];
        let geojson_polygon = Value::Polygon(geojson_multi_line_string_type1);
        let geo_polygon: geo_types::Polygon<f64> = geojson_polygon.try_into().unwrap();

        let geo_line_string1 = geo_polygon.exterior();
        assert_almost_eq!(geo_line_string1.0[0].x, coord1[0], 1e-6);
        assert_almost_eq!(geo_line_string1.0[0].y, coord1[1], 1e-6);
        assert_almost_eq!(geo_line_string1.0[1].x, coord2[0], 1e-6);
        assert_almost_eq!(geo_line_string1.0[1].y, coord2[1], 1e-6);
        assert_almost_eq!(geo_line_string1.0[2].x, coord3[0], 1e-6);
        assert_almost_eq!(geo_line_string1.0[2].y, coord3[1], 1e-6);
        assert_almost_eq!(geo_line_string1.0[3].x, coord1[0], 1e-6);
        assert_almost_eq!(geo_line_string1.0[3].y, coord1[1], 1e-6);

        assert_eq!(0, geo_polygon.interiors().len());
    }

    #[test]
    fn geojson_multi_polygon_conversion_test() {
        let coord1 = vec![100.0, 0.0];
        let coord2 = vec![101.0, 1.0];
        let coord3 = vec![101.0, 1.0];
        let coord4 = vec![104.0, 0.2];
        let coord5 = vec![100.9, 0.2];
        let coord6 = vec![100.9, 0.7];

        let geojson_line_string_type1 = vec![
            coord1.clone(),
            coord2.clone(),
            coord3.clone(),
            coord1.clone(),
        ];

        let geojson_line_string_type2 = vec![
            coord4.clone(),
            coord5.clone(),
            coord6.clone(),
            coord4.clone(),
        ];
        let geojson_multi_polygon = Value::MultiPolygon(vec![
            vec![geojson_line_string_type1],
            vec![geojson_line_string_type2],
        ]);
        let geo_multi_polygon: geo_types::MultiPolygon<f64> =
            geojson_multi_polygon.try_into().unwrap();

        let geo_line_string1 = geo_multi_polygon.0[0].exterior();
        assert_almost_eq!(geo_line_string1.0[0].x, coord1[0], 1e-6);
        assert_almost_eq!(geo_line_string1.0[0].y, coord1[1], 1e-6);
        assert_almost_eq!(geo_line_string1.0[1].x, coord2[0], 1e-6);
        assert_almost_eq!(geo_line_string1.0[1].y, coord2[1], 1e-6);
        assert_almost_eq!(geo_line_string1.0[2].x, coord3[0], 1e-6);
        assert_almost_eq!(geo_line_string1.0[2].y, coord3[1], 1e-6);
        assert_almost_eq!(geo_line_string1.0[3].x, coord1[0], 1e-6);
        assert_almost_eq!(geo_line_string1.0[3].y, coord1[1], 1e-6);

        let geo_line_string2 = geo_multi_polygon.0[1].exterior();
        assert_almost_eq!(geo_line_string2.0[0].x, coord4[0], 1e-6);
        assert_almost_eq!(geo_line_string2.0[0].y, coord4[1], 1e-6);
        assert_almost_eq!(geo_line_string2.0[1].x, coord5[0], 1e-6);
        assert_almost_eq!(geo_line_string2.0[1].y, coord5[1], 1e-6);
        assert_almost_eq!(geo_line_string2.0[2].x, coord6[0], 1e-6);
        assert_almost_eq!(geo_line_string2.0[2].y, coord6[1], 1e-6);
        assert_almost_eq!(geo_line_string2.0[3].x, coord4[0], 1e-6);
        assert_almost_eq!(geo_line_string2.0[3].y, coord4[1], 1e-6);
    }

    #[test]
    fn geojson_geometry_collection_conversion_test() {
        let coord1 = vec![100.0, 0.0];
        let coord2 = vec![100.0, 1.0];
        let coord3 = vec![101.0, 1.0];
        let coord4 = vec![102.0, 0.0];
        let coord5 = vec![101.0, 0.0];

        let geojson_multi_point = Value::MultiPoint(vec![coord1.clone(), coord2.clone()]);
        let geojson_multi_line_string = Value::MultiLineString(vec![
            vec![coord1.clone(), coord2.clone()],
            vec![coord2.clone(), coord3.clone()],
        ]);
        let geojson_multi_polygon = Value::MultiPolygon(vec![
            vec![vec![
                coord3.clone(),
                coord4.clone(),
                coord5.clone(),
                coord3.clone(),
            ]],
            vec![vec![
                coord1.clone(),
                coord5.clone(),
                coord3.clone(),
                coord1.clone(),
            ]],
        ]);

        let geojson_geometry_collection = Value::GeometryCollection(vec![
            Geometry::new(geojson_multi_point),
            Geometry::new(geojson_multi_line_string),
            Geometry::new(geojson_multi_polygon),
        ]);

        let geo_geometry_collection: geo_types::GeometryCollection<f64> =
            geojson_geometry_collection.try_into().unwrap();

        assert_eq!(3, geo_geometry_collection.0.len());
    }

    #[test]
    fn geojson_geometry_conversion() {
        let coords = vec![100.0, 0.2];
        let geojson_geometry = Geometry::from(Value::Point(coords.clone()));
        let geo_geometry: geo_types::Geometry<f64> = geojson_geometry
            .try_into()
            .expect("Should be able to convert to geo_types::Geometry");
        let geo_point: geo_types::Point<_> =
            geo_geometry.try_into().expect("this should be a point");
        assert_almost_eq!(geo_point.x(), coords[0], 1e-6);
        assert_almost_eq!(geo_point.y(), coords[1], 1e-6);
    }

    #[test]
    fn geojson_mismatch_geometry_conversion_test() {
        let coord1 = vec![100.0, 0.2];
        let coord2 = vec![101.0, 1.0];
        let geojson_line_string = Value::LineString(vec![coord1.clone(), coord2.clone()]);
        use std::convert::TryFrom;
        let error = geo_types::Point::<f64>::try_from(geojson_line_string).unwrap_err();
        assert_eq!(
            "Expected type: `Point`, but found `LineString`",
            format!("{}", error)
        )
    }

    #[test]
    fn feature_collection_with_geom_collection() {
        let geojson_str = json!({
            "type": "FeatureCollection",
            "features": [
            {
                "type": "Feature",
                "geometry": {
                    "type": "GeometryCollection",
                    "geometries": [
                    {
                        "type": "Polygon",
                        "coordinates": [
                            [
                                [1.0, 1.0],
                                [2.0, 2.0],
                                [3.0, 1.0],
                                [1.0, 1.0]
                            ]
                        ]
                    }
                    ]
                },
                "properties": {}
            }
            ]
        })
        .to_string();
        let geojson: crate::GeoJson = geojson_str.parse().unwrap();
        let mut geojson_feature_collection: crate::FeatureCollection = geojson.try_into().unwrap();
        let feature: crate::Feature = geojson_feature_collection.features.remove(0);

        use std::convert::TryFrom;
        let geo_geom = geo_types::Geometry::try_from(feature).unwrap();

        let expected =
            geo_types::Geometry::GeometryCollection(geo_types::GeometryCollection(vec![
                geo_types::Geometry::Polygon(geo_types::Polygon::new(
                    geo_types::LineString::new(vec![
                        geo_types::coord!(x: 1.0, y: 1.0),
                        geo_types::coord!(x: 2.0, y: 2.0),
                        geo_types::coord!(x: 3.0, y: 1.0),
                        geo_types::coord!(x: 1.0, y: 1.0),
                    ]),
                    vec![],
                )),
            ]));
        assert_eq!(geo_geom, expected);
    }

    #[test]
    fn borrowed_value_conversions_test() -> crate::Result<()> {
        let coord1 = vec![100.0, 0.2];
        let coord2 = vec![101.0, 1.0];
        let coord3 = vec![102.0, 0.8];
        let coord4 = vec![104.0, 0.2];

        let geojson_point = Value::Point(coord1.clone());
        let _: geo_types::Point<f64> = (&geojson_point).try_into()?;

        let geojson_multi_point = Value::MultiPoint(vec![coord1.clone(), coord2.clone()]);
        let _: geo_types::MultiPoint<f64> = (&geojson_multi_point).try_into()?;

        let geojson_line_string = Value::LineString(vec![coord1.clone(), coord2.clone()]);
        let _: geo_types::LineString<f64> = (&geojson_line_string).try_into()?;

        let geojson_multi_line_string = Value::MultiLineString(vec![
            vec![coord1.clone(), coord2.clone()],
            vec![coord2.clone(), coord3.clone()],
        ]);
        let _: geo_types::MultiLineString<f64> = (&geojson_multi_line_string).try_into()?;

        let geojson_multi_line_string_type1 = vec![
            vec![
                coord1.clone(),
                coord2.clone(),
                coord3.clone(),
                coord1.clone(),
            ],
            vec![
                coord4.clone(),
                coord1.clone(),
                coord2.clone(),
                coord4.clone(),
            ],
        ];
        let geojson_polygon = Value::Polygon(geojson_multi_line_string_type1);
        let _: geo_types::Polygon<f64> = (&geojson_polygon).try_into()?;

        let geojson_line_string_type1 = vec![
            coord1.clone(),
            coord2.clone(),
            coord3.clone(),
            coord1.clone(),
        ];

        let geojson_line_string_type2 = vec![
            coord4.clone(),
            coord3.clone(),
            coord2.clone(),
            coord4.clone(),
        ];
        let geojson_multi_polygon = Value::MultiPolygon(vec![
            vec![geojson_line_string_type1],
            vec![geojson_line_string_type2],
        ]);
        let _: geo_types::MultiPolygon<f64> = (&geojson_multi_polygon).try_into()?;

        let geojson_geometry_collection = Value::GeometryCollection(vec![
            Geometry::new(geojson_multi_point),
            Geometry::new(geojson_multi_line_string),
            Geometry::new(geojson_multi_polygon),
        ]);

        let _: geo_types::GeometryCollection<f64> = (&geojson_geometry_collection).try_into()?;

        Ok(())
    }
}
