"""
NMEA0183 verarbeiten

TODO Multi-Sentence verarbeiten
  TXT Textmeldungen (Alarm)
  RTE Routendaten
  AIS-Sentences mit Binärdaten
"""

import serial
from setproctitle import setthreadtitle
import pynmea2

# Empfangsthread
def rxd_0183(appdata,boatdata, devname):
    # Prüfe ob NMEA0183-Port vorhanden ist und sich öffnen läßt
    try:
        ser = serial.Serial(devname, 115200, timeout=3)
    except serial.SerialException as e:
        print("NMEA0183 serial port not available")
        return
    setthreadtitle("0183listener")
    while not appdata.shutdown:
        raw = ser.readline().decode('ascii')
        if len(raw.strip()) == 0:
            continue
        try:
            msg = pynmea2.parse(raw)
        except pynmea2.nmea.ParseError:
            print(f"NMEA0183: Parse-Error: {raw}", end='')
            continue
        # sentence_type kann fehlen
        try:
            stype = msg.sentence_type
        except:
            print(f"NMEA0183: Sentence type missing: {raw}")
            continue
        # WIP Neuer Code aus Modul
        # TODO Filter mit gewünschen Satztypen
        # if stype in stypefilter:
        #     continue
        if stype in decoder:
            decoder[stype](boatdata, msg)
        else:
            # Hier unbekannter Satztyp: protokollieren und ignorieren
            """
            ['checksum', 'data', 'fields', 'identifier', 'name_to_idx', 'parse',
             'proprietary_re', 'query_re', 'render', 'sentence_re',
             'sentence_type', 'sentence_types', 'talker', 'talker_re']
            """
            print(f"Nicht implementiert: '{stype}' from {msg.talker}")

    ser.close()

def DBS(boatdata, msg):
    # Wassertiefe unter der Oberfläche
    pass
    #boatdata.setValue("DBS", msg.depth)

def DBT(boatdata, msg):
    # Wassertiefe unter Geber
    pass
    #boatdata.setValue("DBT", msg.depth)

def DPT(boatdata, msg):
    # Depth
    print("-> DPT")
    print(msg.fields)
    print(msg.data)
    #boatdata.setValue("DBT", msg.depth)

def GBS(boatdata, msg):
    # GNSS satellite fault detection
    """
(('Timestamp', 'timestamp', <function timestamp at 0x7f59cb0b65c0>), ('Expected error in latitude', 'lat_err'), ('Expected error in longitude', 'lon_err'), ('Expected error in altitude', 'alt_err'), ('PRN of most likely failed satellite', 'sat_prn_num_f'), ('Probability of missed detection for most likely failed satellite', 'pro_miss', <class 'decimal.Decimal'>), ('Estimate of bias in meters on most likely failed satellite', 'est_bias'), ('Standard deviation of bias estimate', 'est_bias_dev'))
['213024.00', '0.9', '0.6', '2.5', '', '', '', '']
NMEA0183: Parse-Error: !AIVDM,1,1,,A,H3ti3hPpDhiT0    """
    print("-> GBS")
    print(msg.fields)
    print(msg.data)
    #boatdata.setValue("LAT", msg.latitude)

def GGA(boatdata, msg):
    # Time, position, and fix related data
    # msg.num_sats
    # msg.timestamp
    if msg.gps_qual == 0:
        # No fix
        return
    if msg.lat_dir == 'N':
        boatdata.setValue("LAT", msg.latitude)
    else:
        boatdata.setValue("LAT", msg.latitude * -1)
    if msg.lon_dir == 'E':
        boatdata.setValue("LON", msg.longitude)
    else:
        boatdata.setValue("LON", msg.longitude * -1)
    boatdata.setValue("HDOP", msg.horizontal_dil)

def GLL(boatdata, msg):
    # Position data: position fix, time of position fix, and status
    if not msg.status == 'A':
        return
    lat_fac = 1 if msg.lat_dir == 'N' else -1
    lat_deg = int(msg.lat[0:2])
    lat_min = float(msg.lat[2:])
    boatdata.setValue("LAT", lat_fac * lat_deg + lat_min / 60)
    lon_fac = 1 if msg.lon_dir == 'E' else -1
    lon_deg = int(msg.lon[0:3])
    lon_min = float(msg.lon[3:])
    boatdata.setValue("LON", lon_fac * lon_deg + lon_min / 60)
    boatdata.setValue("TSPOS", msg.timestamp) # datetime.time, UTC

def GSA(boatdata, msg):
    # Satellites
    for i in range(1, 13):
        satno = getattr(msg, f"sv_id{i:02}")
        if (len(satno) > 0) and not satno in boatdata.sat:
            boatdata.addSatellite(int(satno))
    boatdata.setValue("PDOP", float(msg.pdop))
    boatdata.setValue("PDOP", float(msg.hdop))
    boatdata.setValue("PDOP", float(msg.vdop))

def GSV(boatdata, msg):
    # Satellites in view
    # mgs_num msg.num_messages # Nachricht n von m
    # msg.num_sv_in_view # Anzahl sichtbarer Satelliten
    rres = None  # range residuals
    for i in range(1, 5):
        prn_num = getattr(msg, f"sv_prn_num_{i}")
        if len(prn_num) > 0:
            elevation = float(getattr(msg, f"elevation_deg_{i}"))
            azimuth = float(getattr(msg, f"azimuth_{i}"))
            snr = getattr(msg, f"snr_{i}")
            if len(snr) == 0:
                snr = 0
                status = 1 # prnusage tracked
            else:
                status = 2 # prnusage used
            boatdata.updateSatellite(int(prn_num), elevation, azimuth, int(snr), rres, status)
    """
    if msg.sv_prn_num_1:
        if msg.snr_1 == '':
            status = 1
            msg.snr_1 = 0
        boatdata.updateSatellite(int(msg.sv_prn_num_1), float(msg.elevation_deg_1), float(msg.azimuth_1), int(msg.snr_1), rres, status)
    if msg.sv_prn_num_2:
        if msg.snr_2 == '':
            status = 1
            msg.snr_2 = 0
        boatdata.updateSatellite(int(msg.sv_prn_num_2), float(msg.elevation_deg_2), float(msg.azimuth_2), int(msg.snr_2), rres, status)
    if msg.sv_prn_num_3:
        if msg.snr_3 == '':
            status = 1
            msg.snr_3 = 0
        boatdata.updateSatellite(int(msg.sv_prn_num_3), float(msg.elevation_deg_3), float(msg.azimuth_3), int(msg.snr_3), rres, status)
    if msg.sv_prn_num_4:
        if msg.snr_4 == '':
            status = 1
            msg.snr_4 = 0
        boatdata.updateSatellite(int(msg.sv_prn_num_4), float(msg.elevation_deg_4), float(msg.azimuth_4), int(msg.snr_4), rres, status)
    """

def HDG(boatdata, msg):
    # UNUSED: Heading  - Deviation & Variation
    # Magnetic Sensor heading in degrees
    # msg.heading
    # .deviation, dev_dir E/W
    # .variation, var_dir E/W
    pass

def HDM(boatdata, msg):
    # Heading magnetic
    if msg.magnetic == 'M':
        boatdata.setValue("HDM", msg.heading)
    else:
        print("HDM: M not set!")

def HDT(boatdata, msg):
    # Heading True
    if msg.hdg_true == 'T':
        boatdata.setValue("HDT", msg.heading)
    else:
        print("HDT: T not set!")

def HTD(boatdata, msg):
    # Heading/Track control data
    # e.g. $YDHTD,V,1.5,,R,N,,,,,,,,,A,,,*48
    print("-> HTD")
    print(msg.fields)

def MWV(boatdata, msg):
    # Windgeschwindigkeit und -winkel
    print(f"Wind: {msg.wind_angle}° {msg.wind_speed}kt")
    boatdata.setValue("AWA", msg.wind_angle)
    boatdata.setValue("AWS", msg.wind_speed)

def MTW(boatdata, msg):
    # Wassertemperatur
    # boatdata.setValue("WTemp", msg.xxx)
    print("-> MTW Wassertemperatur")

def RMB(boatdata, msg):
    # Recommended Minimum Navigation Information
    # Informationen bzgl. Erreichen des nächsten Wegepunkts
    #
    # (('Status', 'status'),
    # ('Cross Track Error', 'cross_track_error'),
    #('Cross Track Error, direction to corrent', 'cte_correction_dir'),
    #('Origin Waypoint ID', 'origin_waypoint_id'),
    # ('Destination Waypoint ID', 'dest_waypoint_id'),
    #('Destination Waypoint Latitude', 'dest_lat'),
    #('Destination Waypoint Lat Direction', 'dest_lat_dir'),
    # ('Destination Waypoint Longitude', 'dest_lon'),
    #('Destination Waypoint Lon Direction', 'dest_lon_dir'),
    #('Range to Destination', 'dest_range'),
    #('True Bearing to Destination', 'dest_true_bearing'),
    #('Velocity Towards Destination', 'dest_velocity'),
    #('Arrival Alarm', 'arrival_alarm'))
    print("-> RMB")
    if not msg.status == 'A':
        return
    lat_fac = 1 if msg.dest_lat_dir == 'N' else -1
    lat_deg = int(msg.dest_lat[0:2])
    lat_min = float(msg.dest_lat[2:])
    lon_fac = 1 if msg.dest_lon_dir == 'E' else -1
    lon_deg = int(msg.dest_lon[0:3])
    lon_min = float(msg.dest_lon[3:])
    boatdata.setValue("WPLat", lat_fac * lat_deg + lat_min / 60)
    boatdata.setValue("WPLon", lon_fac * lon_deg + lon_min / 60)
    boatdata.setValue("WPname", msg.dest_waypoint_id)
    boatdata.setValue("DTW", float(msg.dest_range))
    boatdata.setValue("BTW", float(msg.dest_true_bearing))

def RMC(boatdata, msg):
    # Recommended Minimum Navigation Information
    #print("-> RMC")
    # print(msg.timestamp, msg.datestamp)
    # print(msg.status) # V=Warning, P=Precise, A=OK
    # mag_variation, mag_var_dir E/W
    # TODO nav_status welche Bedeutung?
    if not msg.status == 'A':
        return
    lat_fac = 1 if msg.lat_dir == 'N' else -1
    lat_deg = int(msg.lat[0:2])
    lat_min = float(msg.lat[2:])
    boatdata.setValue("LAT", lat_fac * lat_deg + lat_min / 60)
    lon_fac = 1 if msg.lon_dir == 'E' else -1
    lon_deg = int(msg.lon[0:3])
    lon_min = float(msg.lon[3:])
    boatdata.setValue("LON", lon_fac * lon_deg + lon_min / 60)
    if msg.spd_over_grnd:
        boatdata.setValue("SOG", float(msg.spd_over_grnd))
    if msg.true_course:
        boatdata.setValue("COG", float(msg.true_course))

def ROT(boatdata, msg):
    # Rate Of Turn
    # print("-> ROT")
    if msg.status == 'A':
        boatdata.setValue("ROT", msg.rate_of_turn)

def RSA(boatdata, msg):
    # Rudder Sensor Angle
    # negative Werte bedeuten Backbord
    #print("-> RSA")
    # Boatdata: RPOS primär, PRPOS sekundär
    if msg.rsa_starboard_status== 'A':
        boatdata.setValue("RPOS", msg.rsa_starboard)
    if msg.rsa_port_status == 'A':
        boatdata.setValue("PRPOS", msg.rsa_port)

rte_curr = 0
rte_max = 0
rte_wpl = []

def RTE(boatdata, msg):
    # Route: List of Waypoints
    # num_in_seq, sen_num, start_type, active_route_id
    global rte_curr, rte_max
    nmax = int(msg.sen_num)
    n = int(msg.num_in_seq)
    if nmax > 1:
        if rte_curr == 0 and n == 1:
            # neue Nachricht
            pass
        else:
            # Fortsetzung
            pass
    else:
        pass
    print("-> RTE")
    print(msg.fields)
    print(msg.waypoint_list)

txt_msg = ''
txt_type = None
txt_curr = 0
txt_max = 0

def TXT(boatdata, msg):
    # Text Transmission (e.G. Alarms)
    global txt_msg, txt_type, txt_curr, txt_max
    #print("-> TXT")
    nmax = int(msg.num_msg)
    n = int(msg.msg_num)
    if nmax > 1:
        if txt_curr == 0 and n == 1:
            # neue Nachricht
            txt_msg = msg.text.rstrip('\r\n')
            txt_type = msg.msg_type
            txt_curr = 1
            txt_max = nmax
        else:
            # Fortsetzung
            if txt_curr == n - 1 and txt_type == msg.msg_type:
                txt_msg += msg.text.rstrip('\r\n')
                txt_curr = n
            if n == nmax:
                # Vollständig!
                print(f"TXT: {msg.msg_type} - {txt_msg}")
                if not boatdata.alarm:
                    # Momentan wird kein bereits anstehender Alarm überschrieben
                    boatdata.alarm_msg = txt_msg.strip()
                    boatdata.alarm = True
                    boatdata.alarm_id = msg.msg_type
                    boatdata.alarm_src = "NMEA0183"
                txt_curr = 0
                txt_max = 0
    else:
        print(f"TXT: {msg.msg_type} - {msg.text}", end='')
        if not boatdata.alarm:
            # Momentan wird kein bereits anstehender Alarm überschrieben
            boatdata.alarm_msg = msg.text.strip()
            boatdata.alarm = True
            boatdata.alarm_id = msg.msg_type
            boatdata.alarm_src = "NMEA0183"

def VBW(boatdata, msg):
    print("-> VBW")

def VHW(boatdata, msg):
    # Heading und Geschwindigkeit durch das Wasser
    # Aktuelle Meßdaten von Sensoren
    # msg.heading_true # degrees decimal
    # msg.heading_magnetic degrees decimal
    # print("-> VHW")
    if msg.heading_true is not None:
        boatdata.setValue("HDT", float(msg.heading_true))
    if msg.heading_magnetic is not None:
        boatdata.setValue("HDM", float(msg.heading_magnetic))
    boatdata.setValue("STW", float(msg.water_speed_knots))

def VPW(boatdata, msg):
    # UNUSED: Speed - Measured Parallel to Wind
    # print(f"-> VPW: {msg.speed_kn} kn")
    pass

def VTG(boatdata, msg):
    """
    Track made good and speed over ground
    Calculated from previous GPS positions
    Only true track is used at the moment. magnetic is ignored
    """
    if msg.faa_mode != 'A':
        return
    #TODO klären was für Typen hier ankommen können
    # bytearray, str, decimal.Decimal?
    #str von OpenCPN: sog = float(msg.spd_over_grnd_kts[:-1])
    #Ggf. ist OpenCPN buggy!
    cog = float(msg.true_track)        # in Grad
    sog = float(msg.spd_over_grnd_kts) # in Knoten
    boatdata.setValue("COG", cog)
    boatdata.setValue("SOG", sog)

def VWR(boatdata, msg):
    # Relative Wind Speed and Angle
    #print("-> VWR")
    if msg.l_r == "R":
        angle = msg.deg_r
    else:
        angle = 360 - msg.deg_r 
    boatdata.setValue("AWA", angle)
    boatdata.setValue("AWS", msg.wind_speed_ms)

def WPL(boatdata, msg):
    # Waypoint
    # lat, lat_dir
    # lon, lon_dir
    # waypoint_id (name)
    print("-> WPL")
    print(msg.fields)

    lat_fac = 1 if msg.lat_dir == 'N' else -1
    lat_deg = int(msg.lat[0:2])
    lat_min = float(msg.lat[2:])
    #boatdata.setValue("LAT", lat_fac * lat_deg + lat_min / 60)
    lon_fac = 1 if msg.lon_dir == 'E' else -1
    lon_deg = int(msg.lon[0:3])
    lon_min = float(msg.lon[3:])
    #boatdata.setValue("LON", lon_fac * lon_deg + lon_min / 60)

    # Prüfe, ob der Wegepunkt schon existiert
    if msg.waypoint_id in boatdata.wps:
        # Wegepunkt aktualisieren
        pass
    else:
        # neuen Wegepunkt anlegen
        # boatdata.wps[waypoint_id]
        pass


def VWT(boatdata, msg):
    # True Wind Speed and Angle
    if msg.direction == "R":
        angle = msg.wind_angle_vessel
    else:
        angle = 360 - msg.wind_angle_vessel
    boatdata.setValue("TWA", angle)
    boatdata.setValue("TWS", msg.wind_speed_meters)

def XDR(boatdata, msg):
    # Extra sensor data / Transducer Measurement
    # type, value, units, id
    # type: A
    # units: D
    # id: Yaw
    if msg.id.lower() == 'yaw':
        boatdata.setValue("YAW", float(msg.value))
    elif msg.id.lower() == 'ptch':
        boatdata.setValue("PTCH", float(msg.value))
    elif msg.id.lower() == 'roll':
        boatdata.setValue("ROLL", float(msg.value))
    elif msg.id.lower() == 'barometer':
        boatdata.setValue("xdrPress", float(msg.value))
    else:
        print(f"-> XDR: {msg.type}, {msg.value}, {msg.units}, {msg.id}")

def XTE(boatdata, msg):
    # Cross Track error measured
    print("-> XTE")

def XTR(boatdata, msg):
    # Cross Track error gekoppelt
    print("-> XTR")

def ZDA(boatdata, msg):
    # Time and date
    print("-> XTE")
    #boatdata.gpsd
    #boatdata.gpst

# AIS
def VDM(boatdata, msg):
    print("-> VDM")
    print(msg.fields)
    print(msg.data)

def VDO(boatdata, msg):
    print("-> VDO")


# Aus Performancegründen eine direkte Sprungtabelle, ggf. können
# zukünftig außer der Funktion noch weitere Daten gespeichert werdeb
decoder = {
    "DBS": DBS,
    "DBT": DBT,
    "DPT": DPT,
    "GBS": GBS,
    "GGA": GGA,
    "GLL": GLL,
    "GSA": GSA,
    "GSV": GSV,
    "HDG": HDG,
    "HDM": HDM,
    "HDT": HDT,
    "HTD": HTD,
    "MWV": MWV,
    "MTW": MTW,
    "RMB": RMB,
    "ROT": ROT,
    "RMC": RMC,
    "RSA": RSA,
    "RTE": RTE,
    "TXT": TXT,
    "VBW": VBW,
    "VHW": VHW,
    "VPW": VPW,
    "VTG": VTG,
    "VWR": VWR,
    "VWT": VWT,
    "WPL": WPL,
    "XDR": XDR,
    "XTE": XTE,
    "XTR": XTR,
    "ZDA": ZDA,

    "VDM": VDM
}
