"""
Functions for computing navigational information. Can be used to add
navigational information to DataFrames.
"""

from math import acos, asin, atan2, cos, sin, degrees, radians, sqrt


def gcd_slc(
    lon0: float,
    lat0: float,
    lon1: float,
    lat1: float,
) -> float:
    """
    Compute great circle distance on earth surface between two locations.

    Parameters
    ----------
    lon0 : float
        Longitude of position 0
    lat0 : float
        Latitude of position 0
    lon1 : float
        Longitude of position 1
    lat1 : float
        Latitude of position 1

    Returns
    -------
    dist : float
        Great circle distance between position 0 and position 1.

    """
    if abs(lat0 - lat1) <= 1e-6 and abs(lon0 - lon1) <= 1e-6:
        return 0

    r_earth = 6371

    # Convert to radians
    lat0, lat1, lon0, lon1 = map(radians, [lat0, lat1, lon0, lon1])

    return r_earth * acos(
        sin(lat0) * sin(lat1) + cos(lat0) * cos(lat1) * cos(lon1 - lon0)
    )


def haversine(
    lon0: float,
    lat0: float,
    lon1: float,
    lat1: float,
) -> float:
    """
    Compute Haversine distance between two points.

    Parameters
    ----------
    lon0 : float
        Longitude of position 0
    lat0 : float
        Latitude of position 0
    lon1 : float
        Longitude of position 1
    lat1 : float
        Latitude of position 1

    Returns
    -------
    dist : float
        Haversine distance between position 0 and position 1.

    """
    lat0, lat1, dlon, dlat = map(
        radians, [lat0, lat1, lon1 - lon0, lat1 - lat0]
    )
    if abs(dlon) < 1e-6 and abs(dlat) < 1e-6:
        return 0

    r_earth = 6371

    a = sin(dlat / 2) ** 2 + cos(lat0) * cos(lat1) * sin(dlon / 2) ** 2
    c = 2 * asin(sqrt(a))
    return c * r_earth


def bearing(
    lon0: float,
    lat0: float,
    lon1: float,
    lat1: float,
) -> float:
    """
    Compute the bearing of a track from (lon0, lat0) to (lon1, lat1).

    Duplicated from geo-py

    Parameters
    ----------
    lon0 : float,
        Longitude of start point
    lat0 : float,
        Latitude of start point
    lon1 : float,
        Longitude of target point
    lat1 : float,
        Latitude of target point

    Returns
    -------
    bearing : float
        The bearing from point (lon0, lat0) to point (lon1, lat1) in degrees.
    """
    lon0, lat0, lon1, lat1 = map(radians, [lon0, lat0, lon1, lat1])

    dlon = lon1 - lon0
    numerator = sin(dlon) * cos(lat1)
    denominator = cos(lat0) * sin(lat1) - (sin(lat0) * cos(lat1) * cos(dlon))

    theta = atan2(numerator, denominator)
    theta_deg = (degrees(theta) + 360) % 360
    return theta_deg


def destination(
    lon: float, lat: float, bearing: float, distance: float
) -> tuple[float, float]:
    """
    Compute destination of a great circle path.

    Compute the destination of a track started from 'lon', 'lat', with
    'bearing'. Distance is in units of km.

    Duplicated from geo-py

    Parameters
    ----------
    lon : float
        Longitude of initial position
    lat : float
        Latitude of initial position
    bearing : float
        Direction of track
    distance : float
        Distance to travel

    Returns
    -------
    destination : tuple[float, float]
        Longitude and Latitude of final position
    """
    lon, lat = radians(lon), radians(lat)
    radians_bearing = radians(bearing)
    r_earth = 6371
    delta = distance / r_earth

    lat2 = asin(
        sin(lat) * cos(delta) + cos(lat) * sin(delta) * cos(radians_bearing)
    )
    numerator = sin(radians_bearing) * sin(delta) * cos(lat)
    denominator = cos(delta) - sin(lat) * sin(lat2)

    lon2 = lon + atan2(numerator, denominator)

    lon2_deg = (degrees(lon2) + 540) % 360 - 180
    lat2_deg = degrees(lat2)

    return lon2_deg, lat2_deg


def midpoint(
    lon0: float,
    lat0: float,
    lon1: float,
    lat1: float,
) -> tuple[float, float]:
    """
    Compute the midpoint of a great circle track

    Parameters
    ----------
    lon0 : float
        Longitude of position 0
    lat0 : float
        Latitude of position 0
    lon1 : float
        Longitude of position 1
    lat1 : float
        Latitude of position 1

    Returns
    -------
    lon, lat
        Positions of midpoint between position 0 and position 1

    """
    bear = bearing(lon0, lat0, lon1, lat1)
    dist = haversine(lon0, lat0, lon1, lat1)

    return destination(lon0, lat0, bear, dist / 2)