import RXTools as rx
import re

class DColRT17Config:
    """Use to set or Trimcomm RT17 config. Read is 0x49 COMMOUT, and write is 0x51 COMMCTRL.
 Example:
  x = DColRT17Config('10.1.2.3',5017, io_port=21)
  print(x)
  x.set_fft_msecs(1000)
  x.set_bands(['L1','L2'])
  x.send_conf()
  print(x)
    """
    msec_lookup = [0,100,200,1000,2000,5000,10000,30000,60000,300000,600000,500,15000,50,0,
                   20,10,3000]
    mode_str = ['avg','max','avg+max']
    miti_str = ['pre','post','pre+post']
    ant_str = ['0','1','0+1']
    possible_bands = ['L1','L2','L5','E6','B1','S1','G1','G2','G3','E5B','B3']
    def __init__(self, IPAddr, port, io_port=None):
        self.IPAddr = IPAddr
        self.port = port
        self.io_port = io_port
        self.fftInterval = 0
        self.fftConfig = 0
        self.get_conf()

    def send_conf(self, verbose=False):
        """Send latest configuration to the receiver"""
        retVal = False
        epoch_rate = self.msecs // 100
        values = [self.io_port+1,
                  1,
                  47, # RT17
                  epoch_rate>>8,
                  epoch_rate&0xff]
        values.extend( self.flags )
        values.extend( self.extras )
        values.extend( [(self.fftInterval>>8)&0xff,
                        (self.fftInterval>>0)&0xff] )
        values.extend( [(self.fftConfig>>24)&0xff,
                        (self.fftConfig>>16)&0xff,
                        (self.fftConfig>> 8)&0xff,
                        (self.fftConfig>> 0)&0xff] )
        try:
            s = rx.DColSocket( self.IPAddr, self.port )
            if verbose:
                print("Sending Trimcomm 0x49 with data",values)
            data = s.send_recv( 0x49, values )
            if(data[0] == 0x06):
                print("OK",data)
                retVal = True
            else:
                print("Failed to set config",data)
        finally:
            del s
        self.get_conf()
        return retVal

    def set_fft_msecs(self,msecs):
        """Set FFT interval. Gets rounded to multiple of 100"""
        if msecs >= 100:
            self.fftInterval = (msecs//100)*100
        elif msecs > 0:
            self.fftInterval = 100
        else:
            self.fftInterval = 0

    def set_meas_pos_msecs(self,msecs):
        """Set meas/pos interval. Gets divided by 100 before sending to receiver."""
        self.msecs = msecs

    def set_bands(self,bands):
        """Set FFT bands we want data from."""
        for band in bands:
            if not band in self.possible_bands:
                raise ValueError(f"{band} must be in {self.possible_bands}")
        self.bands = bands
        for idx in range(15,26):
            self.fftConfig &= ~(1 << idx)
        for band in bands:
            idx = self.possible_bands.index(band) + 15
            self.fftConfig |= (1 << idx)

    def set_mode(self,mode):
        """Set FFT mode. Use one of: avg/max/avg+max"""
        if not mode in self.mode_str:
            raise ValueError(f"mode must be in {self.mode_str}")
        self.mode = mode
        self.fftConfig &= ~0x3
        self.fftConfig |= self.mode_str.index(mode)

    def set_miti(self,miti):
        """Set FFT mitigation source. Set to one of: pre/post/pre+post"""
        if not miti in self.miti_str:
            raise ValueError(f"miti must be in {self.miti_str}")
        self.miti = miti
        self.fftConfig &= ~(0x3<<2)
        self.fftConfig |= self.miti_str.index(miti)<<2

    def decode_conf(self):
        """Convert fftConfig into a more readable format"""
        mode = self.fftConfig & 3
        self.mode = self.mode_str[mode]
        miti = (self.fftConfig>>2) & 3
        self.miti = self.miti_str[miti]
        ant = (self.fftConfig>>4) & 3
        self.ant = self.ant_str[ant]
        self.add_tot = ((self.fftConfig>>6)&1)==1
        self.add_rssi = ((self.fftConfig>>7)&1)==1
        self.add_qual = ((self.fftConfig>>8)&1)==1
        spacing = (self.fftConfig>>9) & 7
        self.spacing = 2**spacing
        pts = (self.fftConfig>>12) & 7
        if pts == 0:
            self.points = 'all'
        else:
            self.points = 2**pts
        self.bands = []
        for idx in range(15,26):
            if (self.fftConfig>>idx)&1 :
                band = self.possible_bands[idx-15]
                if self.points == 'all':
                    band = 'all-'+band
                self.bands.append( band )
        self.add_fe_rssi = ((self.fftConfig>>26)&1)==1

    def get_conf(self):
        """Get latest RT17 config from the receiver"""
        try:
            s = rx.DColSocket( self.IPAddr, self.port )
            if self.io_port is None:
                data = s.send_recv( 0x6f, [] )
                self.io_port = int(re.findall(rb';PORT,([0-9]+),',data)[0])
            data = s.send_recv( 0x51, [0x5,self.io_port+1,47] )
            packet_len = data[3]
            # io_port = data[5] == self.io_port
            # output = data[6] == 47 = RT17
            self.msecs = self.msec_lookup[data[7]]
            self.flags = data[8:11]
            self.extras = data[11:16]
            if packet_len >= 18:
                # extras6-11
                self.fftInterval = (data[16]<<8)|data[17]
                self.fftConfig = (data[18]<<24)|(data[19]<<16)|(data[20]<<8)|data[21]
                self.decode_conf()
        finally:
            del s

    def __str__(self):
        desc = ''
        desc += f'Meas/pos msecs {self.msecs}\n'
        desc += f'FFT msecs {self.fftInterval}\n'
        desc += f'Config 0x{self.fftConfig:x}\n'
        desc += f'Points {self.points}\n'
        desc += f'Mode {self.mode}\n'
        desc += f'Miti {self.miti}\n'
        desc += f'Antenna {self.ant}\n'
        desc += f'Spacing div {self.spacing}\n'
        desc += f'Total gain {self.add_tot}\n'
        desc += f'RSSI {self.add_rssi}\n'
        desc += f'Band quality {self.add_qual}\n'
        desc += f'Front-end RSSI {self.add_fe_rssi}\n'
        desc += f'Bands {self.bands}\n'
        return desc[:-1]
