import migen

from litex.build import io

from amaranth.lib import wiring

class PadsProxy:
    def __init__(self, wrapper, name, migen_pads, dir, xdr):
        self.signature = wiring.Signature(members = [])

        if isinstance(migen_pads, migen.Record):
            if dir is None:
                dir = {}
            elif isinstance(dir, str):
                dir = {subname: dir for subname, *_ in migen_pads.layout}

            if xdr is None:
                xdr = {}

            assert isinstance(dir, dict) or isinstance(dir, str)
            assert isinstance(xdr, dict)

            for subname, *_ in migen_pads.layout:
                subsignal = PadsProxy(wrapper, f'{name}_{subname}', getattr(migen_pads, subname), dir.get(subname), xdr.get(subname))
                setattr(self, subname, subsignal)
                self.signature.members[subname] = wiring.Out(subsignal.signature)

        elif isinstance(migen_pads, migen.Signal):
            if dir is None:
                dir = '-'
            if xdr is None:
                xdr = 0
            
            if dir == '-':
                pass

            elif xdr == 0:
                if dir == 'i':
                    self.i = wrapper.from_migen(migen_pads, name = f'{name}_i')
                    self.signature.members['i'] = wiring.Out(self.i.shape())
                elif dir == 'o':
                    self.o = wrapper.from_migen(migen_pads, name = f'{name}_o')
                    self.signature.members['o'] = wiring.In(self.o.shape())
                elif dir == 'io':
                    i = migen.Signal.like(migen_pads)
                    o = migen.Signal.like(migen_pads)
                    oe = migen.Signal()
                    wrapper.specials += io.Tristate(migen_pads, o, migen.Replicate(oe, migen_pads.nbits), i)
                    self.i = wrapper.from_migen(i, name = f'{name}_i')
                    self.signature.members['i'] = wiring.Out(self.i.shape())
                    self.o = wrapper.from_migen(o, name = f'{name}_o')
                    self.signature.members['o'] = wiring.In(self.o.shape())
                    self.oe = wrapper.from_migen(oe, name = f'{name}_oe')
                    self.signature.members['oe'] = wiring.In(self.oe.shape())
                elif dir == 'oe':
                    i = migen.Signal.like(migen_pads)
                    o = migen.Signal.like(migen_pads)
                    oe = migen.Signal()
                    wrapper.specials += io.Tristate(migen_pads, i = i, o = o, oe = migen.Replicate(oe, migen_pads.nbits))
                    self.o = wrapper.from_migen(o, name = f'{name}_o')
                    self.signature.members['o'] = wiring.In(self.o.shape())
                    self.oe = wrapper.from_migen(oe, name = f'{name}_oe')
                    self.signature.members['oe'] = wiring.In(self.oe.shape())
                else:
                    raise RuntimeError(f'{xdr=} and {dir=} not supported yet')

            elif xdr == 1:
                if dir == 'i':
                    i = migen.Signal.like(migen_pads)
                    i_clk = migen.Signal()
                    wrapper.specials += io.SDRInput(i = migen_pads, o = i, clk = i_clk)
                    self.i = wrapper.from_migen(i, name = f'{name}_i')
                    self.signature.members['i'] = wiring.Out(self.i.shape())
                    self.i_clk = wrapper.from_migen(i_clk, name = f'{name}_i_clk')
                    self.signature.members['i_clk'] = wiring.In(self.i_clk.shape())
                elif dir == 'o':
                    o = migen.Signal.like(migen_pads)
                    o_clk = migen.Signal()
                    wrapper.specials += io.SDROutput(i = o, o = migen_pads, clk = o_clk)
                    self.o = wrapper.from_migen(o, name = f'{name}_o')
                    self.signature.members['o'] = wiring.In(self.o.shape())
                    self.o_clk = wrapper.from_migen(o_clk, name = f'{name}_o_clk')
                    self.signature.members['o_clk'] = wiring.In(self.o_clk.shape())
                elif dir == 'io':
                    i = migen.Signal.like(migen_pads)
                    _i = migen.Signal.like(migen_pads)
                    o = migen.Signal.like(migen_pads)
                    _o = migen.Signal.like(migen_pads)
                    oe = migen.Signal()
                    i_clk = migen.Signal()
                    o_clk = migen.Signal()
                    wrapper.specials += io.Tristate(migen_pads, i = _i, o = _o, oe = migen.Replicate(oe, migen_pads.nbits))
                    for idx in range(migen_pads.nbits):
                        wrapper.specials += io.SDRInput(i = _i[idx], o = i[idx], clk = i_clk)
                        wrapper.specials += io.SDROutput(i = o[idx], o = _o[idx], clk = o_clk)
                    self.i = wrapper.from_migen(i, name = f'{name}_i')
                    self.signature.members['i'] = wiring.Out(self.i.shape())
                    self.o = wrapper.from_migen(o, name = f'{name}_o')
                    self.signature.members['o'] = wiring.In(self.o.shape())
                    self.oe = wrapper.from_migen(oe, name = f'{name}_oe')
                    self.signature.members['oe'] = wiring.In(self.oe.shape())
                    self.i_clk = wrapper.from_migen(i_clk, name = f'{name}_i_clk')
                    self.signature.members['i_clk'] = wiring.In(self.i_clk.shape())
                    self.o_clk = wrapper.from_migen(o_clk, name = f'{name}_o_clk')
                    self.signature.members['o_clk'] = wiring.In(self.o_clk.shape())
                elif dir == 'oe':
                    _i = migen.Signal.like(migen_pads)
                    o = migen.Signal.like(migen_pads)
                    _o = migen.Signal.like(migen_pads)
                    oe = migen.Signal()
                    o_clk = migen.Signal()
                    wrapper.specials += io.Tristate(migen_pads, i = _i, o = _o, oe = migen.Replicate(oe, migen_pads.nbits))
                    for idx in range(migen_pads.nbits):
                        wrapper.specials += io.SDROutput(i = o[idx], o = _o[idx], clk = o_clk)
                    self.o = wrapper.from_migen(o, name = f'{name}_o')
                    self.signature.members['o'] = wiring.In(self.o.shape())
                    self.oe = wrapper.from_migen(oe, name = f'{name}_oe')
                    self.signature.members['oe'] = wiring.In(self.oe.shape())
                    self.o_clk = wrapper.from_migen(o_clk, name = f'{name}_o_clk')
                    self.signature.members['o_clk'] = wiring.In(self.o_clk.shape())
                else:
                    raise RuntimeError(f'{xdr=} and {dir=} not supported yet')

            else:
                raise RuntimeError(f'{xdr=} not supported yet')

        assert self.signature.is_compliant(self)

    def __str__(self):
        return f'<PadsProxy: {self.signature}>'

class PlatformProxy:
    def __init__(self, wrapper):
        self.wrapper = wrapper

    def request(self, name, number = None, *, dir = None, xdr = None):
        migen_pads = self.wrapper.platform.request(name, number)

        if number is not None:
            name = f'{name}_{number}'

        return PadsProxy(self.wrapper, f'pad_{name}', migen_pads, dir, xdr)