import os
import struct
import pymoku
import pymoku.slot
from pymoku.network.render import CacheableRenderControl
from pymoku.registers import RegisterAccess
from spiregs import SPIRegMap


class SimRenderControl(CacheableRenderControl):
    def __init__(self):
        self._refs = []

        self._cached_state = {
            'cbuf0': {
                '_phy_offset': 34359738368,
                '_phy_size': 6442450944,
            },
        }
        self._cached_write = {}
        self._cached = True


class CoCoTBSlot(pymoku.Slot):
    """ Abuse the cacheable layer to gather write commands
        call await apply() from cocotb context to write regs
    """
    def __init__(self, dut):
        RegisterAccess.__init__(self)
        self._Slot__inputs = {}
        self._Slot__outputs = {}
        self.dut = dut
        self.spi = None
        self._CachableAccessControl__cached = True
        self.num_regs = 128
        self.slot = 0
        block = b'\x00' * self.num_regs * 4
        self._CachableAccessControl__cached_regs = list(struct.unpack(f'<{self.num_regs:d}I', block))
        self._CachableAccessControl__to_write = []
        self.rdr_ctrl = SimRenderControl()
        self.init_regs(**self.parse_generics())

    def parse_generics(self):
        generics = os.getenv('TEST_GENERICS').split(' ')
        generics = [g.split('=') for g in generics]
        g = dict()
        for k, v in generics:
            # try to guess the type, we can only do so much...
            k = k.lower()
            try:
                g[k] = int(v)  # integer
            except ValueError:
                if v.lower() in ['true', 'false']:
                    g[k] = str(v.lower() == 'true')  # boolean
                else:
                    g[k] = v  # string

        self.num_ain = g['num_ain']
        self.num_aout = g['num_aout']
        self.num_cbufs = g['num_cbufs']
        return g

    def init_regs(self, **kwargs):
        pass

    def cbufs(self):
        return [f'cbuf{i:d}' for i in range(self.num_cbufs)]

    async def apply(self):
        if self.spi is None:
            self.spi = SPIRegMap(self.dut, freq=50e6)

        if self._CachableAccessControl__to_write:
            await self.spi.writeregs(self._CachableAccessControl__to_write)

        # block = await self.spi.readblock(0, 4 * self.num_regs)
        # self._CachableAccessControl__cached_regs = list(struct.unpack(f'<{self.num_regs:d}I', block))
        self._CachableAccessControl__to_write = []

    def _fetch_state(self):
        pass

    def _close_state(self):
        pass



# monkey patch
pymoku.Slot = CoCoTBSlot
pymoku.slot.Slot = CoCoTBSlot


# import the actual testbench
star = {k: v for k, v in vars(__import__(os.getenv('TEST_MODULE'), fromlist=['*'])).items()
        if not k.startswith('_')}
globals().update(star)


class SimMoku(object):
    num_slots = 1
    AXI_BUS_WIDTH = 16

    def sys_ref(self):
        return CLK_FREQ  # from the cocotb testbench

    def adc_sample_rate(self):
        return self.sys_ref() * 4.0


CoCoTBSlot.moku = SimMoku()
