import re
from datetime import datetime, timezone, tzinfo
from dataclasses import dataclass
from mapper_checkout import messages
from . import gps_util

@dataclass
class SystemState:
    host: str = None
    date: str = None
    date_time: datetime = None
    date_valid: str = None
    ntp_synchronized: bool = None
    last_ntp_log: list = None

    def set_datetime(self, dt:datetime):
        self.date_time = dt
        #check if near running pc
        delta = datetime.now(tz=timezone.utc) - dt
        self.date_valid = delta.total_seconds() > -1 and delta.total_seconds() < 1 
        print(f'time offset = {delta.total_seconds()}')


@dataclass
class FanState:
    fan_temp_c: float = None
    fan_external_temp_c: float = None
    mpx2200_pressure_kpa: float = None
    sht1x_temp_c: float = None
    sht1x_humidity_rh: float = None
    last_response: list = None

@dataclass
class BatteryState:
    min_voltage_v: float = None
    max_voltage_v: float = None
    avg_voltage_v: float = None
    avg_capacity_ah: float = None
    total_current_a: float = None
    total_power_w: float = None
    temp_c: float = None
    humidity_rh: float = None
    last_response: list = None
    a_switch_discharge: bool = None
    b_switch_discharge: bool = None
    c_switch_discharge: bool = None

@dataclass
class BeaconState:
    address: int = None
    tat_ms:  int = None
    cis: int = None
    responder_mode: int = None
    last_response: list = None


@dataclass
class SvpState:
    sound_speed_mps: float = None
    sound_speed_valid: bool = False
    last_response: list = None

    def set_sound_speed(self, value:float):
        self.sound_speed_mps = value
        if self.sound_speed_mps > 1100 and self.sound_speed_mps < 1600:
            self.sound_speed_valid = True
        else: self.sound_speed_valid = False

@dataclass
class BenthosModemState:
    s_registers: list = None
    last_response: list = None

@dataclass
class ParosciPressureState:
    temp_c: float = None
    depth_m: float = None
    pressure_dbar: float = None
    surface_pressure: float = None
    last_response: list = None

@dataclass
class FastcatCtdState:
    salinity_psu: float = None
    velocity_mps: float = None
    conductivity_mSm: float = None
    temp_c: float = None
    pressure_dbar: float = None
    last_message: list = None

@dataclass
class GpsState:
    lat_ddm: str = None
    lon_ddm: str = None
    num_sats: float = None
    hdop: float = None
    altitude: float = None
    last_message: list = None

@dataclass
class KearfottInsState:
    counter: int = None
    code: int = None
    mode: bytes = None
    monitor: bytes = None
    lat_dd: float = None
    lon_dd: float = None
    depth_m: float = None
    height_m: float = None
    roll_deg: float = None
    pitch_deg: float = None
    heading_deg: float = None
    doppler_processing: bool = None
    gga_lat_dd: float = None
    gga_lon_dd: float = None
    last_message: list = None
    position_error_m: float = None

    def __repr__(self):
        strrepr =  f'mode: {self.mode} monitor: {self.monitor}\r\n'
        strrepr += f'lat: {self.lat_dd} lon: {self.lon_dd} depth: {self.depth_m}\r\n'
        strrepr += f'heading: {self.heading_deg} pitch: {self.pitch_deg} roll: {self.roll_deg}\r\n'
        strrepr += f'doppler_processing: {self.doppler_processing}\r\n'
        strrepr += f'position_error: {self.position_error_m}\r\n'
        return strrepr


class MapperState:
    ''' Class to store vital information collected from the MVC '''

    def __init__(self, host) -> None:
        
        self.host = host
        self.system = SystemState()
        self.fan_card = FanState()
        self.aft_battery = BatteryState()
        self.fwd_battery = BatteryState()
        self.beacon = BeaconState()
        self.modem = BenthosModemState()
        self.pressure = ParosciPressureState()
        self.svp = SvpState()
        self.ctd = FastcatCtdState()
        self.gps = GpsState()
        self.ins = KearfottInsState()

    def print_info(info:str) -> None:
        print('[INFO] '+ info)

    def __repr__(self):
        rep = f'[PARSED INFO]: {self.host}\r\n\r\n'
        rep+= f'[PARSED INFO]: {self.system}\r\n\r\n'
        rep+= f'[PARSED INFO]: {self.fan_card}\r\n\r\n'
        rep+= f'[PARSED INFO]: {self.aft_battery}\r\n\r\n'
        rep+= f'[PARSED INFO]: {self.fwd_battery}\r\n\r\n'
        rep+= f'[PARSED INFO]: {self.beacon}\r\n\r\n'
        rep+= f'[PARSED INFO]: {self.modem}\r\n\r\n'
        rep+= f'[PARSED INFO]: {self.pressure}\r\n\r\n'
        rep+= f'[PARSED INFO]: {self.svp}\r\n\r\n'
        rep+= f'[PARSED INFO]: {self.ctd}\r\n\r\n'
        rep+= f'[PARSED INFO]: {self.gps}\r\n\r\n'
        return rep

    def parse_date(self, resp:list):
        self.system.date = resp[0].decode('ascii')
        self.system.set_datetime(datetime.strptime(self.system.date, '%a %b %d %H:%M:%S %Z %Y').replace(tzinfo=timezone.utc))
        #print(self.system)
        pass

    def parse_ntp(self, resp:list):
        self.system.last_ntp_log = resp[0].decode('ascii')
        self.system.ntp_synchronized = re.search('synchronized to GPS_NMEA',self.system.last_ntp_log) != None
        #print(self.system)

    def parse_fan_card(self, resp:list):
        self.fan_card.last_response = resp
        for line in resp:
            line = line.decode('ascii')
            if re.search('Fan Card', line):
                self.fan_card.fan_temp_c = float(line.split()[3].split('/')[0])
            if re.search('External', line):
                self.fan_card.fan_external_temp_c = float(line.split()[2].split('/')[0])
            if re.search('^MPX2200', line):
                self.fan_card.mpx2200_pressure_kpa = float(line.split()[3])
            if re.search('^SHT1x Temp', line):
                self.fan_card.sht1x_temp_c = float(line.split()[3].split('/')[0])
            if re.search('^SHT1x Humidity', line):
                self.fan_card.sht1x_humidity_rh = float(line.split()[3])
        
        print(self.fan_card)


    def parse_beacon(self, resp:list):
        self.beacon.last_response = resp[0].decode('ascii')
        fields = self.beacon.last_response.split(',')
        for field in fields:
            if re.search('^>CS:', field):
                self.beacon.address = int(field.strip('>CIS:'))
            elif re.search('^TAT', field):
                self.beacon.tat_ms = int(field.strip('TAT'))
            elif re.search('^CIS', field):
                self.beacon.cis = int(field.strip('CIS'))
            elif re.search('^RSP', field):
                self.beacon.responder_mode = int(field.strip('RSP'))

        #print(self.beacon)

    def parse_modem(self, resp:list):
        pass

    def parse_battery(self, resp:list, position:str='fwd'):
        batt = BatteryState()
        batt.last_response = resp
        for item in resp:
            line = item.decode('ascii')
            fields = line.split()
            if re.search(messages.battery['min_volt'], line):
                batt.min_voltage_v = float(fields[2].strip('V'))
            if re.search(messages.battery['max_volt'], line):
                batt.max_voltage_v = float(fields[2].strip('V'))
            if re.search(messages.battery['avg_volt'], line):
                batt.avg_voltage_v = float(fields[2].strip('V'))
            if re.search(messages.battery['avg_capacity'], line):
                batt.avg_capacity_ah = float(fields[2].strip('Ah'))
            if re.search(messages.battery['total_current'], line):
                batt.total_current_a = float(fields[2].strip('A'))
            if re.search(messages.battery['total_power'], line):
                batt.total_power_w = float(fields[2].strip('W'))
            if re.search(messages.battery['temp'], line):
                batt.temp_c = float(fields[2].strip('*C'))
            if re.search(messages.battery['humidity'], line):
                batt.humidity_rh = float(fields[1].strip('%'))

        if position == 'fwd':
            self.fwd_battery = batt
        else:
            self.aft_battery = batt

    def parse_svp(self, resp:list):
        line = resp[0].decode('ascii')
        self.svp.set_sound_speed(float(line.strip()))
        self.svp.last_response = resp
        #print(self.svp)

    def parse_fastcat(self, resp:list):
        self.ctd.last_message = resp
        for line in resp:
            line = line.decode('ascii')
            fields = line.split()
            print(line)
            if re.search('^salinity', line):
                self.ctd.salinity_psu = float(fields[1].strip(','))
                self.ctd.velocity_mps = float(fields[3])
            if re.search('^cond', line):
                self.ctd.conductivity_mSm = float(fields[2].strip(','))
                self.ctd.temp_c = float(fields[5].strip(','))
                self.ctd.pressure_dbar = float(fields[8].strip(','))
        #print(self.ctd)


    def parse_parosci(self, resp:list):
        self.pressure.last_response = resp
        line = resp[0].decode('ascii')
        fields = line.split()
        self.pressure.depth_m = float(fields[1].strip(','))
        self.pressure.temp_c = float(fields[3].strip(','))
        self.pressure.pressure_dbar = float(fields[5].strip(','))
        self.pressure.surface_pressure = float(fields[8].strip(','))

        #print(self.pressure)


    def parse_gps(self, resp:list):
        self.gps.last_message = resp
        for item in resp:
            line = item.decode('ascii')
            if re.search(messages.gps['latlon'], line) != None:
                line = line.split(' ')
                self.gps.lat_ddm = line[1] + ' ' + line[2].strip(',')
                self.gps.lon_ddm = line[4] + ' ' + line[5].strip(',')
            elif re.search(messages.gps['num_sats_alt'], line) != None:
                line = line.split(' ')
                self.gps.num_sats = int(line[1].strip(','))
                self.gps.hdop = float(line[3].strip(','))
                self.gps.altitude = float(line[5].strip(','))

        #print(self.gps)

    def parse_kearfott(self, resp:list):
        self.ins.last_message = resp
        for item in resp:
            line = item.decode('ascii')
            fields = line.split(' ')
            if re.search(messages.kearfott['cntr'], line) != None:
                self.ins.counter = int(fields[1])
                self.ins.mode = fields[4]
                self.ins.monitor = fields[7]
            elif re.search(messages.kearfott['lat'], line) != None:
                self.ins.lat_dd = float(fields[1])
                self.ins.lon_dd = float(fields[4])
                self.ins.depth_m = float(fields[6])
                self.ins.height_m = float(fields[9])
            elif re.search(messages.kearfott['roll'], line) != None:
                self.ins.roll_deg = float(fields[1])
                self.ins.pitch_deg = float(fields[3])
                self.ins.heading_deg = float(fields[5])
            elif re.search(messages.kearfott['bottom'], line) != None:
                dopp_bottom_stat = fields[3]
                self.ins.doppler_processing = dopp_bottom_stat != '0x00'
            elif re.search(messages.kearfott['gps_gga'], line) != None:
                lat_str = re.split('\(|\)', fields[3])[1]
                lon_str = re.split('\(|\)', fields[4])[1]
                self.ins.gga_lat_dd = float(lat_str)
                self.ins.gga_lon_dd = float(lon_str)
                #BUG current kearfott driver does not assign correct sign to the incoming GGA string when parsing
                if self.ins.lon_dd < 0 and self.ins.gga_lon_dd > 0:
                    self.ins.gga_lon_dd *= -1

            #update meta variables
            if self.ins.lat_dd != None and self.ins.lon_dd != None and self.ins.gga_lat_dd != None and self.ins.gga_lon_dd != None:
                kilometer_error = gps_util.calc_distance(self.ins.lat_dd, self.ins.lon_dd, self.ins.gga_lat_dd, self.ins.gga_lon_dd)
                self.ins.position_error_m = kilometer_error*1000.0
                
                
        print(self.ins)        



    




    
