import time

from app.CsvWriter import CsvWriter

METER_SEC_TO_KNOT = 1.943844

METER_SEC_TO_KMH = 3.6


class NemaBase:

    @staticmethod
    def checksum(nmea_data):
        cksum = 0
        for s in nmea_data:
            cksum ^= ord(s)
        return cksum

    @staticmethod
    def decimal_deg_to_deg_min_sec(dd):
        minutes, seconds = divmod(abs(dd) * 3600, 60)
        degrees, minutes = divmod(minutes, 60)

        return degrees, minutes, seconds

    def decimal_deg_to_nema_dec_min(self, dd, coordinate_type):

        is_positive = dd >= 0
        degrees, minutes, seconds = self.decimal_deg_to_deg_min_sec(dd)
        decimal_min = minutes + seconds/60

        direction = ""
        nema_decimal_min = ""
        if coordinate_type == 'lat':
            nema_decimal_min = f"{degrees:02.0F}{decimal_min:.3F}"
            direction = 'N' if is_positive else 'S'

        elif coordinate_type == 'lon':
            nema_decimal_min = f"{degrees:03.0F}{decimal_min:.3F}"
            direction = 'E' if is_positive else 'W'

        return nema_decimal_min, direction

    def wrap_nema(self, nema_payload):
        return "$" + nema_payload + "*{:02X}".format(self.checksum(nema_payload))


class NemaGGA(NemaBase):

    def __init__(self, log_path, cfg):
        # data needed for GGA NEMA sentence
        self.time = None  # epoch seconds, GMT
        self.lat = None  # latitude, decimal degrees
        self.lon = None  # longitude, decimal degrees
        self.depth = None  # meters, negative down

        self.csv_writer = CsvWriter('GGA', log_path, cfg['gga_csv_headers'], cfg['max_csv_log_duration_hr'])

    def log_to_csv(self):
        self.csv_writer.write(self.get_data())

    def assemble_msg(self):
        return self.assemble(self.time, self.lat, self.lon, self.depth)

    def assemble(self, epoch_time: float, lat_dd: float, lon_dd: float, depth: float):

        time_str = time.strftime('%H%M%S', time.gmtime(epoch_time))
        lat, lat_dir = self.decimal_deg_to_nema_dec_min(lat_dd, 'lat')
        lon, lon_dir = self.decimal_deg_to_nema_dec_min(lon_dd, 'lon')

        gga = self.wrap_nema(f"GPGGA,{time_str},{lat},{lat_dir},{lon},{lon_dir},1,08,0.9,{depth:.1F},M,46.9,M,,")

        return gga

    def update_time(self, new_time, max_time_diff=1.0):

        if self.time is None:
            self.time = new_time

        elif abs(self.time - new_time) >= max_time_diff:
            self.clear_data()
            self.time = new_time

    def get_data(self):
        return [self.time, self.lat, self.lon, self.depth]

    def data_ready(self):

        for val in self.get_data():
            if val is None:
                return False

        return True

    def clear_data(self):
        self.time = None
        self.lat = None
        self.lon = None
        self.depth = None


class NemaVTG(NemaBase):

    def __init__(self, log_path, cfg):
        # data needed for VTG NEMA sentence
        self.time = None  # epoch seconds, GMT
        self.heading = None  # vehicle heading in degrees
        self.speed = None  # vehicle velocity wrt to sea-water in m/s

        self.csv_writer = CsvWriter('VTG', log_path, cfg['vtg_csv_headers'], cfg['max_csv_log_duration_hr'])

    def log_to_csv(self):
        self.csv_writer.write(self.get_data())

    def assemble_msg(self):
        return self.assemble(track_made_good=self.heading, speed_ms=self.speed)

    def assemble(self, track_made_good: float, speed_ms: float):

        speed_knt = speed_ms * METER_SEC_TO_KNOT
        speed_kmh = speed_ms * METER_SEC_TO_KMH

        return self.wrap_nema(f"GPVTG,{track_made_good:.1F},T,XXX.X,M,{speed_knt:.2F},N,{speed_kmh:.2F},K")

    def update_time(self, new_time, max_time_diff=1.0):

        if self.time is None:
            self.time = new_time

        elif abs(self.time - new_time) >= max_time_diff:
            self.clear_data()
            self.time = new_time

    def get_data(self):
        return [self.time, self.heading, self.speed]

    def data_ready(self):
        for val in self.get_data():
            if val is None:
                return False

        return True

    def clear_data(self):
        self.time = None
        self.heading = None
        self.speed = None
