"""Real-time packet layer for satellites"""

from migen import *
from migen.genlib.fsm import *
from migen.genlib.misc import WaitTimer

from artiq.gateware.rtio import cri
from artiq.gateware.drtio.rt_serializer import *


class RTPacketSatellite(Module):
    def __init__(self, link_layer, interface=None):
        self.reset = Signal()

        self.unknown_packet_type = Signal()
        self.packet_truncated = Signal()
        self.buffer_space_timeout = Signal()

        self.tsc_load = Signal()
        self.tsc_load_value = Signal(64)

        self.async_msg_stb = Signal()
        self.async_msg_ack = Signal()

        if interface is None:
            interface = cri.Interface()
        self.cri = interface

        # # #

        # RX/TX datapath
        assert len(link_layer.tx_rt_data) == len(link_layer.rx_rt_data)
        assert len(link_layer.tx_rt_data) % 8 == 0
        ws = len(link_layer.tx_rt_data)
        rx_plm = get_m2s_layouts(ws)
        rx_dp = ReceiveDatapath(
            link_layer.rx_rt_frame, link_layer.rx_rt_data, rx_plm)
        self.submodules += rx_dp
        tx_plm = get_s2m_layouts(ws)
        tx_dp = TransmitDatapath(
            link_layer.tx_rt_frame, link_layer.tx_rt_data, tx_plm)
        self.submodules += tx_dp

        # RX write data buffer
        write_data_buffer_load = Signal()
        write_data_buffer_cnt = Signal(max=512//ws+1)
        write_data_buffer = Signal(512)
        self.sync += \
            If(write_data_buffer_load,
                Case(write_data_buffer_cnt,
                     {i: write_data_buffer[i*ws:(i+1)*ws].eq(rx_dp.data_r)
                      for i in range(512//ws)}),
                write_data_buffer_cnt.eq(write_data_buffer_cnt + 1)
            ).Else(
                write_data_buffer_cnt.eq(0)
            )

        # RX->TX
        echo_req = Signal()
        buffer_space_set = Signal()
        buffer_space_req = Signal()
        buffer_space_ack = Signal()
        self.sync += [
            If(buffer_space_ack, buffer_space_req.eq(0)),
            If(buffer_space_set, buffer_space_req.eq(1)),
        ]

        buffer_space_update = Signal()
        buffer_space = Signal(16)
        self.sync += If(buffer_space_update, buffer_space.eq(self.cri.o_buffer_space))

        load_read_request = Signal()
        clear_read_request = Signal()
        read_request_pending = Signal()
        self.sync += [
            If(clear_read_request | self.reset,
                read_request_pending.eq(0)
            ),
            If(load_read_request,
                read_request_pending.eq(1),
            )
        ]

        self.sync += If(self.async_msg_ack, self.async_msg_stb.eq(0))

        # RX FSM
        cri_read = Signal()
        cri_buffer_space = Signal()
        self.comb += [
            self.tsc_load_value.eq(
                rx_dp.packet_as["set_time"].timestamp),
            If(cri_read | read_request_pending,
                self.cri.chan_sel.eq(
                    rx_dp.packet_as["read_request"].chan_sel),
            ).Elif(cri_buffer_space,
                self.cri.chan_sel.eq(
                    rx_dp.packet_as["buffer_space_request"].destination << 16)
            ).Else(
                self.cri.chan_sel.eq(
                    rx_dp.packet_as["write"].chan_sel),
            ),
            self.cri.i_timeout.eq(
                rx_dp.packet_as["read_request"].timeout),
            self.cri.o_timestamp.eq(
                rx_dp.packet_as["write"].timestamp),
            self.cri.o_address.eq(
                rx_dp.packet_as["write"].address),
            self.cri.o_data.eq(
                Cat(rx_dp.packet_as["write"].short_data, write_data_buffer)),
        ]

        rx_fsm = FSM(reset_state="INPUT")
        self.submodules += rx_fsm

        ongoing_packet_next = Signal()
        ongoing_packet = Signal()
        self.sync += ongoing_packet.eq(ongoing_packet_next)

        timeout_counter = WaitTimer(8191)
        self.submodules += timeout_counter

        rx_fsm.act("INPUT",
            If(rx_dp.frame_r,
                rx_dp.packet_buffer_load.eq(1),
                If(rx_dp.packet_last,
                    Case(rx_dp.packet_type, {
                        # echo must have fixed latency, so there is no memory
                        # mechanism
                        rx_plm.types["echo_request"]: echo_req.eq(1),
                        rx_plm.types["set_time"]: NextState("SET_TIME"),
                        rx_plm.types["write"]: NextState("WRITE"),
                        rx_plm.types["buffer_space_request"]: NextState("BUFFER_SPACE_REQUEST"),
                        rx_plm.types["read_request"]: NextState("READ_REQUEST"),
                        "default": self.unknown_packet_type.eq(1)
                    })
                ).Else(
                    ongoing_packet_next.eq(1)
                ),
                If(~rx_dp.frame_r & ongoing_packet,
                    self.packet_truncated.eq(1)
                )
            )
        )
        rx_fsm.act("SET_TIME",
            self.tsc_load.eq(1),
            NextState("INPUT")
        )

        # CRI mux defaults to write information
        rx_fsm.act("WRITE",
            If(write_data_buffer_cnt == rx_dp.packet_as["write"].extra_data_cnt,
                NextState("WRITE_CMD")
            ).Else(
                write_data_buffer_load.eq(1),
                If(~rx_dp.frame_r,
                    self.packet_truncated.eq(1),
                    NextState("INPUT")
                )
            )
        )
        rx_fsm.act("WRITE_CMD",
            self.cri.cmd.eq(cri.commands["write"]),
            NextState("INPUT")
        )

        rx_fsm.act("BUFFER_SPACE_REQUEST",
            cri_buffer_space.eq(1),
            NextState("BUFFER_SPACE_REQUEST_CMD")
        )
        rx_fsm.act("BUFFER_SPACE_REQUEST_CMD",
            cri_buffer_space.eq(1),
            self.cri.cmd.eq(cri.commands["get_buffer_space"]),
            NextState("BUFFER_SPACE")
        )
        rx_fsm.act("BUFFER_SPACE",
            cri_buffer_space.eq(1),
            timeout_counter.wait.eq(1),
            If(timeout_counter.done,
                self.buffer_space_timeout.eq(1),
                NextState("INPUT")
            ).Elif(self.cri.o_buffer_space_valid,
                buffer_space_set.eq(1),
                buffer_space_update.eq(1),
                NextState("INPUT")
            )
        )

        rx_fsm.act("READ_REQUEST",
            cri_read.eq(1),
            NextState("READ_REQUEST_CMD")
        )
        rx_fsm.act("READ_REQUEST_CMD",
            load_read_request.eq(1),
            cri_read.eq(1),
            self.cri.cmd.eq(cri.commands["read"]),
            NextState("INPUT")
        )

        # TX FSM
        tx_fsm = FSM(reset_state="IDLE")
        self.submodules += tx_fsm

        tx_fsm.act("IDLE",
            If(echo_req, NextState("ECHO")),
            If(self.async_msg_stb, NextState("ASYNC_MESSAGES_READY")),
            If(buffer_space_req, NextState("BUFFER_SPACE")),
            If(read_request_pending & ~self.cri.i_status[2],
                NextState("READ"),
                If(self.cri.i_status[0], NextState("READ_TIMEOUT")),
                If(self.cri.i_status[1], NextState("READ_OVERFLOW"))
            )
        )

        tx_fsm.act("ECHO",
            tx_dp.send("echo_reply"),
            If(tx_dp.packet_last, NextState("IDLE"))
        )

        tx_fsm.act("ASYNC_MESSAGES_READY",
            self.async_msg_ack.eq(1),
            tx_dp.send("async_messages_ready"),
            If(tx_dp.packet_last, NextState("IDLE"))
        )

        tx_fsm.act("BUFFER_SPACE",
            buffer_space_ack.eq(1),
            tx_dp.send("buffer_space_reply", space=buffer_space),
            If(tx_dp.packet_last, NextState("IDLE"))
        )

        tx_fsm.act("READ_TIMEOUT",
            tx_dp.send("read_reply_noevent", overflow=0),
            clear_read_request.eq(1),
            If(tx_dp.packet_last, NextState("IDLE"))
        )
        tx_fsm.act("READ_OVERFLOW",
            tx_dp.send("read_reply_noevent", overflow=1),
            clear_read_request.eq(1),
            If(tx_dp.packet_last, NextState("IDLE"))
        )
        tx_fsm.act("READ",
            tx_dp.send("read_reply",
                       timestamp=self.cri.i_timestamp,
                       data=self.cri.i_data),
            If(tx_dp.packet_last,
                clear_read_request.eq(1),
                NextState("IDLE")
            )
        )
