# ------------------------------------------
#   BDS 0,5
#   ADS-B TC=9-18
#   Airborne position
# ------------------------------------------

from __future__ import annotations

from datetime import datetime

from ... import common


def airborne_position(
    msg0: str, msg1: str, t0: int | datetime, t1: int | datetime
) -> None | tuple[float, float]:
    """Decode airborne position from a pair of even and odd position message

    Args:
        msg0 (string): even message (28 hexdigits)
        msg1 (string): odd message (28 hexdigits)
        t0 (int): timestamps for the even message
        t1 (int): timestamps for the odd message

    Returns:
        (float, float): (latitude, longitude) of the aircraft
    """

    mb0 = common.hex2bin(msg0)[32:]
    mb1 = common.hex2bin(msg1)[32:]

    oe0 = int(mb0[21])
    oe1 = int(mb1[21])
    if oe0 == 0 and oe1 == 1:
        pass
    elif oe0 == 1 and oe1 == 0:
        mb0, mb1 = mb1, mb0
        t0, t1 = t1, t0
    else:
        raise RuntimeError("Both even and odd CPR frames are required.")

    # 131072 is 2^17, since CPR lat and lon are 17 bits each.
    cprlat_even = common.bin2int(mb0[22:39]) / 131072
    cprlon_even = common.bin2int(mb0[39:56]) / 131072
    cprlat_odd = common.bin2int(mb1[22:39]) / 131072
    cprlon_odd = common.bin2int(mb1[39:56]) / 131072

    air_d_lat_even = 360 / 60
    air_d_lat_odd = 360 / 59

    # compute latitude index 'j'
    j = common.floor(59 * cprlat_even - 60 * cprlat_odd + 0.5)

    lat_even = float(air_d_lat_even * (j % 60 + cprlat_even))
    lat_odd = float(air_d_lat_odd * (j % 59 + cprlat_odd))

    if lat_even >= 270:
        lat_even = lat_even - 360

    if lat_odd >= 270:
        lat_odd = lat_odd - 360

    # check if both are in the same latidude zone, exit if not
    if common.cprNL(lat_even) != common.cprNL(lat_odd):
        return None

    # compute ni, longitude index m, and longitude
    # (people pass int+int or datetime+datetime)
    if t0 > t1:  # type: ignore
        lat = lat_even
        nl = common.cprNL(lat)
        ni = max(common.cprNL(lat) - 0, 1)
        m = common.floor(cprlon_even * (nl - 1) - cprlon_odd * nl + 0.5)
        lon = (360 / ni) * (m % ni + cprlon_even)
    else:
        lat = lat_odd
        nl = common.cprNL(lat)
        ni = max(common.cprNL(lat) - 1, 1)
        m = common.floor(cprlon_even * (nl - 1) - cprlon_odd * nl + 0.5)
        lon = (360 / ni) * (m % ni + cprlon_odd)

    if lon > 180:
        lon = lon - 360

    return lat, lon


def airborne_position_with_ref(
    msg: str, lat_ref: float, lon_ref: float
) -> tuple[float, float]:
    """Decode airborne position with only one message,
    knowing reference nearby location, such as previously calculated location,
    ground station, or airport location, etc. The reference position shall
    be within 180NM of the true position.

    Args:
        msg (str): even message (28 hexdigits)
        lat_ref: previous known latitude
        lon_ref: previous known longitude

    Returns:
        (float, float): (latitude, longitude) of the aircraft
    """

    mb = common.hex2bin(msg)[32:]

    cprlat = common.bin2int(mb[22:39]) / 131072
    cprlon = common.bin2int(mb[39:56]) / 131072

    i = int(mb[21])
    d_lat = 360 / 59 if i else 360 / 60

    # From 1090 MOPS, Vol.1  DO-260C, A.1.7.5
    j = common.floor(0.5 + lat_ref / d_lat - cprlat)

    lat = d_lat * (j + cprlat)

    ni = common.cprNL(lat) - i

    if ni > 0:
        d_lon = 360 / ni
    else:
        d_lon = 360

    m = common.floor(0.5 + lon_ref / d_lon - cprlon)

    lon = d_lon * (m + cprlon)

    return lat, lon


def altitude(msg: str) -> None | int:
    """Decode aircraft altitude

    Args:
        msg (str): 28 hexdigits string

    Returns:
        int: altitude in feet
    """

    tc = common.typecode(msg)

    if tc is None or tc < 9 or tc == 19 or tc > 22:
        raise RuntimeError("%s: Not an airborne position message" % msg)

    mb = common.hex2bin(msg)[32:]
    altbin = mb[8:20]

    if tc < 19:
        altcode = altbin[0:6] + "0" + altbin[6:]
        alt = common.altitude(altcode)
        if alt != -999999:
            return alt
        else:
            # return None if altitude is invalid
            return None
    else:
        return common.bin2int(altbin) * 3.28084  # type: ignore
