from typing import Any, Dict, Optional, Union

from pyproj._crs import Datum, Ellipsoid, PrimeMeridian


class CustomDatum(Datum):
    """
    .. versionadded:: 2.5.0

    Class to build a datum based on an ellipsoid and prime meridian.
    """

    def __new__(
        cls,
        name: str = "undefined",
        ellipsoid: Any = "WGS 84",
        prime_meridian: Any = "Greenwich",
    ):
        """
        Parameters
        ----------
        name: str, optional
            Name of the datum. Default is 'undefined'.
        ellipsoid: Any, optional
            Anything accepted by :meth:`pyproj.crs.Ellipsoid.from_user_input`
            or a :class:`pyproj.crs.datum.CustomEllipsoid`.
        prime_meridian: Any, optional
            Anything accepted by :meth:`pyproj.crs.PrimeMeridian.from_user_input`.
        """
        datum_json = {
            "$schema": "https://proj.org/schemas/v0.2/projjson.schema.json",
            "type": "GeodeticReferenceFrame",
            "name": name,
            "ellipsoid": Ellipsoid.from_user_input(ellipsoid).to_json_dict(),
            "prime_meridian": PrimeMeridian.from_user_input(
                prime_meridian
            ).to_json_dict(),
        }
        return cls.from_json_dict(datum_json)


class CustomEllipsoid(Ellipsoid):
    """
    .. versionadded:: 2.5.0

    Class to build a custom ellipsoid.
    """

    def __new__(
        cls,
        name: str = "undefined",
        semi_major_axis: Optional[float] = None,
        inverse_flattening: Optional[float] = None,
        semi_minor_axis: Optional[float] = None,
        radius: Optional[float] = None,
    ):
        """
        Parameters
        ----------
        name: str, optional
            Name of the ellipsoid. Default is 'undefined'.
        semi_major_axis: float, optional
            The semi major axis in meters. Required if missing radius.
        inverse_flattening: float, optional
            The inverse flattening in meters.
            Required if missing semi_minor_axis and radius.
        semi_minor_axis: float, optional
            The semi minor axis in meters.
            Required if missing inverse_flattening and radius.
        radius: float, optional
            The radius in meters. Can only be used alone.
            Cannot be mixed with other parameters.
        """
        ellipsoid_json = {
            "$schema": "https://proj.org/schemas/v0.2/projjson.schema.json",
            "type": "Ellipsoid",
            "name": name,
        }  # type: Dict[str, Union[float, str]]
        if semi_major_axis is not None:
            ellipsoid_json["semi_major_axis"] = semi_major_axis
        if inverse_flattening is not None:
            ellipsoid_json["inverse_flattening"] = inverse_flattening
        if semi_minor_axis is not None:
            ellipsoid_json["semi_minor_axis"] = semi_minor_axis
        if radius is not None:
            ellipsoid_json["radius"] = radius
        return cls.from_json_dict(ellipsoid_json)


class CustomPrimeMeridian(PrimeMeridian):
    """
    .. versionadded:: 2.5.0

    Class to build a prime meridian based on a longitude.
    """

    def __new__(cls, longitude: float, name: str = "undefined"):
        """
        Parameters
        ----------
        longitude: float
            Longitude of prime meridian.
        name: str, optional
            Name of the prime meridian.
        """
        datum_json = {
            "$schema": "https://proj.org/schemas/v0.2/projjson.schema.json",
            "type": "PrimeMeridian",
            "name": name,
            "longitude": longitude,
        }
        return cls.from_json_dict(datum_json)
