#
# Copyright 2020 Ettus Research, a National Instruments Brand
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""This module holds the Emulated registers for a Stream Endpoint Node.
One of these objects is instantiated for each Stream Endpoint Node.
"""
from enum import IntEnum

REG_EPID_SELF = 0x00  # RW
REG_RESET_AND_FLUSH = 0x04  # W
REG_OSTRM_CTRL_STATUS = 0x08  # RW
REG_OSTRM_DST_EPID = 0x0C  # W
REG_OSTRM_FC_FREQ_BYTES_LO = 0x10  # W
REG_OSTRM_FC_FREQ_BYTES_HI = 0x14  # W
REG_OSTRM_FC_FREQ_PKTS = 0x18  # W
REG_OSTRM_FC_HEADROOM = 0x1C  # W
REG_OSTRM_BUFF_CAP_BYTES_LO = 0x20  # R
REG_OSTRM_BUFF_CAP_BYTES_HI = 0x24  # R
REG_OSTRM_BUFF_CAP_PKTS = 0x28  # R
REG_OSTRM_SEQ_ERR_CNT = 0x2C  # R
REG_OSTRM_DATA_ERR_CNT = 0x30  # R
REG_OSTRM_ROUTE_ERR_CNT = 0x34  # R
REG_ISTRM_CTRL_STATUS = 0x38  # RW
REG_OSTRM_THROTTLE = 0x3C  # W

RESET_AND_FLUSH_OSTRM = 1 << 0
RESET_AND_FLUSH_ISTRM = 1 << 1
RESET_AND_FLUSH_CTRL = 1 << 2
RESET_AND_FLUSH_ALL = 0x7

STRM_STATUS_FC_ENABLED = 0x80000000
STRM_STATUS_SETUP_ERR = 0x40000000
STRM_STATUS_SETUP_PENDING = 0x20000000


class SwBuff(IntEnum):
    """The size of the elements in a buffer"""

    BUFF_U64 = 0
    BUFF_U32 = 1
    BUFF_U16 = 2
    BUFF_U8 = 3


class CtrlStatusWord:
    """Represents a Control Status Word

    See mgmt_portal:BUILD_CTRL_STATUS_WORD()
    """

    def __init__(self, cfg_start, xport_lossy, pyld_buff_fmt, mdata_buff_fmt, byte_swap):
        self.cfg_start = cfg_start
        self.xport_lossy = xport_lossy
        self.pyld_buff_fmt = pyld_buff_fmt
        self.mdata_buff_fmt = mdata_buff_fmt
        self.byte_swap = byte_swap

    @classmethod
    def parse(cls, val):
        cfg_start = (val & 1) != 0
        xport_lossy = ((val >> 1) & 1) != 0
        pyld_buff_fmt = SwBuff((val >> 2) & 3)
        mdata_buff_fmt = SwBuff((val >> 4) & 3)
        byte_swap = ((val >> 6) & 1) != 0
        return cls(cfg_start, xport_lossy, pyld_buff_fmt, mdata_buff_fmt, byte_swap)

    def __str__(self):
        return (
            "CtrlStatusWord{{cfg_start: {}, xport_lossy: {}, "
            "pyld_buff_fmt: {}, mdata_buff_fmt: {}, byte_swap: {}}}".format(
                self.cfg_start,
                self.xport_lossy,
                self.pyld_buff_fmt,
                self.mdata_buff_fmt,
                self.byte_swap,
            )
        )


class StreamEpRegs:
    """Represents a set of registers associated with a stream endpoint
    which can be accessed through management packets

    See mgmt_portal.cpp
    """

    def __init__(
        self,
        get_epid,
        set_epid,
        set_dst_epid,
        update_status_out,
        update_status_in,
        cap_pkts,
        cap_bytes,
    ):
        self.get_epid = get_epid
        self.set_epid = set_epid
        self.set_dst_epid = set_dst_epid
        self.log = None
        self.out_ctrl_status = 0
        self.in_ctrl_status = 0
        self.update_status_out = update_status_out
        self.update_status_in = update_status_in
        self.cap_pkts = cap_pkts
        self.cap_bytes = cap_bytes

    def read(self, addr):
        if addr == REG_EPID_SELF:
            return self.get_epid()
        elif addr == REG_OSTRM_CTRL_STATUS:
            return self.out_ctrl_status
        elif addr == REG_ISTRM_CTRL_STATUS:
            return self.in_ctrl_status
        elif addr == REG_OSTRM_BUFF_CAP_BYTES_LO:
            return self.cap_bytes & 0xFFFFFFFF
        elif addr == REG_OSTRM_BUFF_CAP_BYTES_HI:
            return (self.cap_bytes >> 32) & 0xFFFFFFFF
        elif addr == REG_OSTRM_BUFF_CAP_PKTS:
            return self.cap_pkts
        else:
            raise NotImplementedError(
                "Unable to read addr 0x{:08X} from stream ep regs".format(addr)
            )

    def write(self, addr, val):
        if addr == REG_EPID_SELF:
            self.log.debug("Setting EPID to {}".format(val))
            self.set_epid(val)
        elif addr == REG_OSTRM_CTRL_STATUS:
            status = CtrlStatusWord.parse(val)
            self.log.debug("Setting EPID Output Stream Ctrl Status: {}".format(status))
            new_status = self.update_status_out(status)
            self.out_ctrl_status = new_status if new_status is not None else val
        elif addr == REG_RESET_AND_FLUSH:
            self.log.trace("Stream EP Regs Reset and Flush")
        elif addr == REG_OSTRM_DST_EPID:
            self.log.debug("Setting Dest EPID to {}".format(val))
            self.set_dst_epid(val)
        elif REG_OSTRM_FC_FREQ_BYTES_LO <= addr <= REG_OSTRM_FC_HEADROOM:
            pass  # TODO: implement these Flow Control parameters
        elif addr == REG_ISTRM_CTRL_STATUS:
            status = CtrlStatusWord.parse(val)
            self.log.debug("Setting EPID Input Stream Ctrl Status: {}".format(status))
            new_status = self.update_status_in(status)
            self.in_ctrl_status = new_status if new_status is not None else val
        elif addr == REG_OSTRM_THROTTLE:
            self.log.trace(
                f"Setting output stream throttle to {val}. " "Note: Throttle not implemented."
            )
            pass  # TODO: implement throttle control
        else:
            raise NotImplementedError(
                "Unable to write addr 0x{:08X} from stream ep regs".format(addr)
            )
