# Copyright (C) 2019-2022 HERE Global B.V. and its affiliate(s).
# All rights reserved.
#
# This software and other materials contain proprietary information
# controlled by HERE and are protected by applicable copyright legislation.
# Any use and utilization of this software and other materials and
# disclosure to any third parties is conditional upon having a separate
# agreement with HERE for the access, use, utilization or disclosure of this
# software. In the absence of such agreement, the use of the software is not
# allowed.

"""
Utilities to convert geometries from non-standard formats
"""
from typing import Mapping, Sequence, Tuple

from geojson.geometry import Geometry, LineString, Point


def is_geojson_geometry(x) -> bool:
    """
    Check if an object is a mapping that represents a GeoJSON geometry.

    :param x: the object to test
    :return: if the object is in a supported, non-standard format
    """
    return (
        isinstance(x, Mapping)
        and "type" in x
        and x["type"]
        in ["Point", "LineString", "Polygon", "MultiLineString", "MultiPoint", "MultiPolygon"]
        and "coordinates" in x
        and isinstance(x["coordinates"], Sequence)
    )


def _is_point(x: Mapping) -> bool:
    return "longitude" in x and "latitude" in x


def _is_pointseq(x: Sequence) -> bool:
    return bool(x) and all(map(_is_point, x))


def _to_coords(x: Mapping) -> Tuple[float, float]:
    return x["longitude"], x["latitude"]


def is_nonstandard_geometry(x) -> bool:
    """
    Check if an object is a geometry in a non-standard, supported format.

    Supported formats:

    { "longitude": 43.23, "latitude": 10.5 }

    [
        { "longitude": 43.23, "latitude": 10.5 },
        { "longitude": 44.01, "latitude": 11.57 }
    ]

    :param x: the object to test
    :return: if the object is in a supported, non-standard format
    """
    if isinstance(x, Mapping) and _is_point(x):
        return True
    elif isinstance(x, Sequence) and _is_pointseq(x):
        return True
    return False


def nonstandard_to_geometry(x) -> Geometry:
    """
    Convert an object from a non-standard format to a GeoJSON geometry.

    :param x: the object to convert
    :return: the object converted to GeoJSON geometry
    :raises ValueError: if the format of the object is not supported
    """
    if isinstance(x, Mapping) and _is_point(x):
        return Point(coordinates=_to_coords(x))
    elif isinstance(x, Sequence) and _is_pointseq(x):
        return LineString(coordinates=list(map(_to_coords, x)))
    else:
        # This assert makes sure the two functions are in sync, at least in one direction
        assert not is_nonstandard_geometry(x)
        raise ValueError("Object in non-standard format cannot be converted to valid geometry")
