from math import atan2, fmod, log, pi, tan
from typing import Dict, Sequence, Union

from turf.helpers import degrees_to_radians, radians_to_degrees
from turf.helpers import Feature
from turf.invariant import get_coords_from_features


def rhumb_bearing(
    origin: Union[Sequence, Dict, Feature],
    destination: Union[Sequence, Dict, Feature],
    options: Dict = None,
) -> float:
    """
    Takes two {Point|points} and finds the bearing angle between them along a
    Rhumb line
    * i.e. the angle measured in degrees start the north line (0 degrees)

    https://en.wikipedia.org/wiki/Rhumb_line

    :param start: starting point [lng, lat] or Point feature
    :param end: ending point [lng, lat] or Point feature
    :param options: Optional parameters
        [options["final"]]: Calculates the final bearing if True

    :return: bearing from north in decimal degrees
    """
    if not isinstance(options, dict):
        options = {}

    origin = get_coords_from_features(origin, ["Point"])
    destination = get_coords_from_features(destination, ["Point"])
    final = options.get("final", False)

    if final:
        bearing = calculate_rhumb_bearing(destination, origin)
    else:
        bearing = calculate_rhumb_bearing(origin, destination)

    return bearing


def calculate_rhumb_bearing(origin: Sequence, destination: Sequence) -> float:
    """
    Calculates the bearing from origin to destination point along a rhumb line.
    http://www.edwilliams.org/avform.htm#Rhumb
    """
    phi_1 = degrees_to_radians(origin[1])
    phi_2 = degrees_to_radians(destination[1])
    delta_lambda = degrees_to_radians(destination[0] - origin[0])

    # if delta_lambda over 180° take shorter rhumb line across the anti-meridian:
    if abs(delta_lambda) > pi:
        if delta_lambda > 0:
            delta_lambda = -(2 * pi - delta_lambda)
        if delta_lambda < 0:
            delta_lambda = 2 * pi + delta_lambda

    delta_psi = log(tan(phi_2 / 2 + pi / 4) / tan(phi_1 / 2 + pi / 4))
    theta = atan2(delta_lambda, delta_psi)

    return fmod(radians_to_degrees(theta) + 360, 360)
