from dataclasses import dataclass
from abc import abstractmethod
from Utils.Subprocess import check_call
from Tc.TcCollection import TcCollection


class TbfCmd(object):
    """Abstract class for Tbf commands

    Class implements abstract method for all tbf commands
    to specify

   """
    @abstractmethod
    def make_cmd(self):
        pass

    @staticmethod
    def format(to_prepend, value, to_append):
        if value:
            return f"{to_prepend} {value}{to_append}"
        else:
            return ""


@dataclass(eq=True, frozen=True)
class Tbf(TbfCmd):
    """Tbf parameter class

    Class handles the generation of partial command for tbf

    Class requires to have have parameters eq and frozen set
    to true - needed for set eq and hash to decide uniqueness of object,
    hash is generated by default if eq and frozen
    """
    _limit: int = 0
    _burst: int = 2000
    _latency: int = 20

    def make_cmd(self):
        _limit = self.format("", self._limit, "kbit")
        _burst = self.format("burst", self._burst, "kb")
        _latency = self.format("latency", self._latency, "ms")
        return f"rate {_limit} {_burst} {_latency}"


class TbfCollection(TcCollection):
    """Class for composing and running the tbf command

    All classes for tbf parameters are aggregated together
    into one big command and run at once
    """
    def __init__(self, nic):
        super().__init__()
        self._nic = nic

    def apply_cmd(self, operation):
        """Function that loops over all dataclasses and calls netem

        Dataclasses with their respective partial commands based
        on parameters are aggregated and main netem command is run
        """
        _used_dtcls = set()

        for dtcls in self:
            for param_val in dtcls.__dict__.values():
                if param_val:
                    _used_dtcls.add(dtcls)

        check_call(f"tc qdisc {operation} dev {self._nic} parent 1:3 handle 30: tbf "
                   + " ".join([x.make_cmd() for x in _used_dtcls]))

