from types import SimpleNamespace

from migen import *
from migen.genlib.resetsync import AsyncResetSynchronizer
from misoc.interconnect.csr import *

from artiq.gateware.rtio import cri, rtlink
from artiq.gateware.rtio.sed.core import *
from artiq.gateware.rtio.input_collector import *
from artiq.gateware.drtio import (link_layer,
                                  rt_packet_satellite, rt_errors_satellite,
                                  rt_packet_master, rt_controller_master,
                                  rt_packet_repeater, rt_controller_repeater)
from artiq.gateware.drtio.rx_synchronizer import GenericRXSynchronizer


__all__ = ["ChannelInterface", "TransceiverInterface",
           "SyncRTIO",
           "DRTIOSatellite", "DRTIOMaster", "DRTIORepeater"]


class ChannelInterface:
    def __init__(self, encoder, decoders):
        self.rx_ready = Signal()
        self.encoder = encoder
        self.decoders = decoders


class TransceiverInterface(AutoCSR):
    def __init__(self, channel_interfaces, *, async_rx=True):
        self.stable_clkin = CSRStorage()
        self.txenable = CSRStorage(len(channel_interfaces))
        if async_rx:
            for i in range(len(channel_interfaces)):
                name = "rtio_rx" + str(i)
                setattr(self.clock_domains, "cd_"+name, ClockDomain(name=name))
        self.channels = channel_interfaces


async_errors_layout = [
    ("sequence_error", 1),
    ("sequence_error_channel", 16),
    ("collision", 1),
    ("collision_channel", 16),
    ("busy", 1),
    ("busy_channel", 16)
]


class SyncRTIO(Module):
    def __init__(self, tsc, channels, lane_count=8, fifo_depth=128):
        self.cri = cri.Interface()
        self.async_errors = Record(async_errors_layout)

        chan_fine_ts_width = max(max(rtlink.get_fine_ts_width(channel.interface.o)
                                     for channel in channels),
                                 max(rtlink.get_fine_ts_width(channel.interface.i)
                                     for channel in channels))
        assert tsc.glbl_fine_ts_width >= chan_fine_ts_width

        self.submodules.outputs = ClockDomainsRenamer("rio")(
            SED(channels, tsc.glbl_fine_ts_width,
                lane_count=lane_count, fifo_depth=fifo_depth,
                enable_spread=False, report_buffer_space=True,
                interface=self.cri))
        self.comb += self.outputs.coarse_timestamp.eq(tsc.coarse_ts)
        self.sync += self.outputs.minimum_coarse_timestamp.eq(tsc.coarse_ts + 16)

        self.submodules.inputs = ClockDomainsRenamer("rio")(
            InputCollector(tsc, channels, interface=self.cri))

        for attr, _ in async_errors_layout:
            self.comb += getattr(self.async_errors, attr).eq(getattr(self.outputs, attr))


class DRTIOSatellite(Module):
    def __init__(self, tsc, chanif, rx_synchronizer=None):
        self.reset = CSRStorage(reset=1)
        self.reset_phy = CSRStorage(reset=1)
        self.tsc_loaded = CSR()
        self.async_messages_ready = CSR()
        # master interface in the sys domain
        self.cri = cri.Interface()
        self.async_errors = Record(async_errors_layout)

        self.clock_domains.cd_rio = ClockDomain()
        self.clock_domains.cd_rio_phy = ClockDomain()
        self.comb += [
            self.cd_rio.clk.eq(ClockSignal("sys")),
            self.cd_rio_phy.clk.eq(ClockSignal("sys"))
        ]
        reset = Signal()
        reset_phy = Signal()
        reset.attr.add("no_retiming")
        reset_phy.attr.add("no_retiming")
        self.sync += [
            reset.eq(self.reset.storage),
            reset_phy.eq(self.reset_phy.storage)
        ]
        self.specials += [
            AsyncResetSynchronizer(self.cd_rio, reset),
            AsyncResetSynchronizer(self.cd_rio_phy, reset_phy)
        ]

        self.submodules.link_layer = link_layer.LinkLayer(
            chanif.encoder, chanif.decoders)
        self.comb += self.link_layer.rx_ready.eq(chanif.rx_ready)

        if rx_synchronizer is None:
            rx_synchronizer = GenericRXSynchronizer()
            self.submodules += rx_synchronizer

        link_layer_sync = SimpleNamespace(
            tx_aux_frame=self.link_layer.tx_aux_frame,
            tx_aux_data=self.link_layer.tx_aux_data,
            tx_aux_ack=self.link_layer.tx_aux_ack,
            tx_rt_frame=self.link_layer.tx_rt_frame,
            tx_rt_data=self.link_layer.tx_rt_data,

            rx_aux_stb=rx_synchronizer.resync(self.link_layer.rx_aux_stb),
            rx_aux_frame=rx_synchronizer.resync(self.link_layer.rx_aux_frame),
            rx_aux_frame_perm=rx_synchronizer.resync(self.link_layer.rx_aux_frame_perm),
            rx_aux_data=rx_synchronizer.resync(self.link_layer.rx_aux_data),
            rx_rt_frame=rx_synchronizer.resync(self.link_layer.rx_rt_frame),
            rx_rt_frame_perm=rx_synchronizer.resync(self.link_layer.rx_rt_frame_perm),
            rx_rt_data=rx_synchronizer.resync(self.link_layer.rx_rt_data)
        )
        self.submodules.link_stats = link_layer.LinkLayerStats(link_layer_sync, "sys")
        self.submodules.rt_packet = rt_packet_satellite.RTPacketSatellite(
            link_layer_sync, interface=self.cri)
        self.comb += self.rt_packet.reset.eq(self.cd_rio.rst)

        self.sync += If(self.async_messages_ready.re, self.rt_packet.async_msg_stb.eq(1))
        self.comb += self.async_messages_ready.w.eq(self.rt_packet.async_msg_ack)

        self.comb += [
            tsc.load.eq(self.rt_packet.tsc_load),
            tsc.load_value.eq(self.rt_packet.tsc_load_value)
        ]

        self.sync += [
            If(self.tsc_loaded.re, self.tsc_loaded.w.eq(0)),
            If(self.rt_packet.tsc_load, self.tsc_loaded.w.eq(1)),
        ]

        self.submodules.rt_errors = rt_errors_satellite.RTErrorsSatellite(
            self.rt_packet, tsc, self.async_errors)

    def get_csrs(self):
        return ([self.reset, self.reset_phy, self.tsc_loaded, self.async_messages_ready] +
                self.link_layer.get_csrs() + self.link_stats.get_csrs() +
                self.rt_errors.get_csrs())


class DRTIOMaster(Module):
    def __init__(self, tsc, chanif):
        self.submodules.link_layer = link_layer.LinkLayer(
            chanif.encoder, chanif.decoders)
        self.comb += self.link_layer.rx_ready.eq(chanif.rx_ready)

        self.submodules.link_stats = link_layer.LinkLayerStats(self.link_layer, "rtio_rx")
        self.submodules.rt_packet = rt_packet_master.RTPacketMaster(self.link_layer)
        self.submodules.rt_controller = rt_controller_master.RTController(
            tsc, self.rt_packet)

    def get_csrs(self):
        return (self.link_layer.get_csrs() +
                self.link_stats.get_csrs() +
                self.rt_controller.get_csrs())

    @property
    def cri(self):
        return self.rt_controller.cri


class DRTIORepeater(Module):
    def __init__(self, tsc, chanif):
        self.submodules.link_layer = link_layer.LinkLayer(
            chanif.encoder, chanif.decoders)
        self.comb += self.link_layer.rx_ready.eq(chanif.rx_ready)

        self.submodules.link_stats = link_layer.LinkLayerStats(self.link_layer, "rtio_rx")
        self.submodules.rt_packet = rt_packet_repeater.RTPacketRepeater(tsc, self.link_layer)
        self.submodules.rt_controller = rt_controller_repeater.RTController(self.rt_packet)

    def get_csrs(self):
        return (self.link_layer.get_csrs() +
                self.link_stats.get_csrs() +
                self.rt_controller.get_csrs())

    @property
    def cri(self):
        return self.rt_packet.cri
