import struct
import logging

log = logging.getLogger(__name__)


class FrameData(object):
    def __init__(self, data, timebase):
        self.timebase = timebase  # TODO should be encoded in the frame data
        self.parse_header(data)
        self.parse_data()

    def parse_header(self, pkt):
        data = pkt[:128]
        self.rdr_name = bytes(data[0:16]).split(b'\x00')[0].decode('utf-8')
        self.src_name = bytes(data[16:32]).split(b'\x00')[0].decode('utf-8')
        self.waveid, \
            self.adc_temp, \
            self.dac_temp = struct.unpack('<Ihh', data[32:40])
        self.offset, \
            self.rate, \
            self.wordsize, \
            self.frame_length = struct.unpack('<iIII', data[40:56])
        self.num_cbufs = int(data[58])
        self.token = int(data[63])
        self.meta = data[64:88]
        self.act_frame_length, = struct.unpack('<I', data[88:92])
        self.full_frame = bool(data[92])
        self.timestamp, = struct.unpack('<Q', data[93:101])

        self.frame_length = self.frame_length if self.full_frame else self.act_frame_length
        self.raw_data = pkt[128:128 + self.frame_length * self.wordsize]

        meta0 = struct.unpack('<I', self.meta[0:4])[0]
        timebaseid = (meta0 & 0x00000F00) >> 8
        # typeid = (meta0 & 0x0000F000) >> 12

        self.delta_t = 1.0
        self.offset_t = 0.0
        self.yscale = 1.0

        if timebaseid == 0:
            pass
        elif timebaseid == 1:
            self.delta_t = float(struct.unpack('<I', self.meta[4:8])[0])
            self.yscale = struct.unpack('<f', self.meta[8:12])[0]
        elif timebaseid == 2:
            self.delta_t = float(struct.unpack('<I', self.meta[4:8])[0])
            self.offset_t = float(struct.unpack('<I', self.meta[8:12])[0])
            self.yscale = struct.unpack('<f', self.meta[12:6])[0]
        elif timebaseid == 3:
            self.delta_t = struct.unpack('<f', self.meta[4:8])[0]
            self.yscale = struct.unpack('<f', self.meta[8:12])[0]
        elif timebaseid == 4:
            self.delta_t = struct.unpack('<f', self.meta[4:8])[0]
            self.offset_t = struct.unpack('<f', self.meta[8:12])[0]
            self.yscale = struct.unpack('<f', self.meta[12:16])[0]
        else:
            assert False, "Invalid TimebaseID"

        self.delta_t = self.delta_t / self.num_cbufs

        t_min = self.offset_t + self.offset * self.delta_t * self.timebase
        ts = self.delta_t * self.rate * self.timebase
        self.time = [t_min + ts * i for i in range(self.frame_length)]

    def parse_data(self):
        pass


class DDSFrameData(FrameData):
    def parse_data(self):
        fmt = {1: 'b', 2: 'h', 4: 'i', 8: 'q'}[self.wordsize]
        raw = struct.unpack(f'<{self.frame_length:d}{fmt}', self.raw_data)
        self.samples = [float(x) * self.yscale for x in raw]


class PrecisionFrameData(DDSFrameData):
    pass


class MinMaxFrameData(FrameData):
    def parse_data(self):
        fmt = {1: 'b', 2: 'h', 4: 'i', 8: 'q'}[self.wordsize // 2]
        raw = struct.unpack(f'<{self.frame_length * 2:d}{fmt}', self.raw_data)
        scaled = [float(x) * self.yscale for x in raw]
        self.min = scaled[::2]
        self.max = scaled[1::2]


class AndOrFrameData(FrameData):
    def parse_data(self):
        fmt = {1: 'B', 2: 'H', 4: 'I', 8: 'Q'}[self.wordsize]
        raw = struct.unpack(f'<{self.frame_length:d}{fmt}', self.raw_data)
        samples = []
        for b in range(self.wordsize * 8):
            a = [(d >> b) & 0x1 for d in raw]
            samples.append(a)
        self.samples = samples


class RecordFrameData(FrameData):
    def parse_data(self):
        Is, Qs = [], []
        length = self.frame_length if self.full_frame else self.act_frame_length
        for i in range(0, length * self.wordsize, self.wordsize):
            record_bytes = self.raw_data[i:i + self.wordsize]
            I, Q, count = struct.unpack("<iiQ", record_bytes)

            if count <= 2**15:
                I = I * 2**12 * 2**-16 / (4.0 * count)
                Q = Q * 2**12 * 2**-16 / (4.0 * count)
            elif count <= 2**20:
                I = I * 2**17 * 2**-16 / (4.0 * count)
                Q = Q * 2**17 * 2**-16 / (4.0 * count)
            elif count <= 2**25:
                I = I * 2**22 * 2**-16 / (4.0 * count)
                Q = Q * 2**22 * 2**-16 / (4.0 * count)
            elif count <= 2**30:
                I = I * 2**27 * 2**-16 / (4.0 * count)
                Q = Q * 2**27 * 2**-16 / (4.0 * count)
            elif count <= 35:
                I = I * 2**32 * 2**-16 / (4.0 * count)
                Q = Q * 2**32 * 2**-16 / (4.0 * count)
            else:
                I = I * 2**37 * 2**-16 / (4.0 * count)
                Q = Q * 2**37 * 2**-16 / (4.0 * count)
            Is.append(I)
            Qs.append(Q)
        self.samples = dict(I=Is, Q=Qs)


parser_table = {0x0: DDSFrameData,
                0x1: PrecisionFrameData,
                0x2: MinMaxFrameData,
                0x3: AndOrFrameData,
                0xF: RecordFrameData}


def parse(packet_data, timebase=1.0, parser=parser_table.get):
    frames = {}
    i = 0

    try:
        while i < len(packet_data):
            data = packet_data[i:]
            src_name = bytes(data[16:32]).split(b'\x00')[0].decode('utf-8')
            wordsize, frame_length, type_id = struct.unpack('<IIB', data[48:57])

            j = i + 128 + frame_length * wordsize

            try:
                frame = parser_table.get(type_id, RecordFrameData)(packet_data[i:j], timebase)
            except (KeyError, IndexError):
                frame = FrameData(data, timebase)
            i = j

            frames[f'{src_name}'] = frame
    except Exception:
        log.exception("Error parsing frame packet")

    return frames
