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


class NetemCmd(object):
    """Abstract class for Netem commands

    Class implements abstract method for all netem commands
    to specify

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

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



@dataclass(eq=True, frozen=True)
class Loss(NetemCmd):
    """Netem Loss parameter class

    Class handles the generation of partial command for netem
    parameter - loss

    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

    """
    _ratio: float = 0.0
    _correlation: float = 0.0

    def make_cmd(self):
        _ratio = self.format(self._ratio, "%")
        _correlation = self.format(self._correlation, "%")
        return f"loss {_ratio} {_correlation}"


@dataclass(eq=True, frozen=True)
class Duplicate(NetemCmd):
    """Netem Duplicate parameter class

    Class handles the generation of partial command for netem
    parameter - duplicate

    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
    """
    _ratio: float = 0.0
    _correlation: float = 0.0

    def make_cmd(self):
        _ratio = self.format(self._ratio, "%")
        _correlation = self.format(self._correlation, "%")
        return f"duplicate {_ratio} {_correlation}"


@dataclass(eq=True, frozen=True)
class Delay(NetemCmd):
    """Netem Delay parameter class

    Class handles the generation of partial command for netem
    parameter - delay

    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
    """
    _time: int = 0
    _jitter: int = 0
    _correlation: float = 0.0

    def make_cmd(self):
        _time = self.format(self._time, "ms")
        _jitter = self.format(self._jitter, "ms")
        _correlation = self.format(self._correlation, "%")

        return f"delay {_time} {_jitter} {_correlation}"


@dataclass(eq=True, frozen=True)
class Reorder(NetemCmd):
    """Netem Reorder parameter class

    Class handles the generation of partial command for netem
    parameter - reorder

    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
    """
    _ratio: float = 0.0
    _correlation: float = 0.0

    def make_cmd(self):
        _ratio = self.format(self._ratio, "%")
        _correlation = self.format(self._correlation, "%")

        return f"reorder {_ratio} {_correlation}"


@dataclass(eq=True, frozen=True)
class Corrupt(NetemCmd):
    """Netem Corrupt parameter class

    Class handles the generation of partial command for netem
    parameter - corrupt

    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
    """
    _ratio: float = 0.0
    _correlation: float = 0.0

    def make_cmd(self):
        _ratio = self.format(self._ratio, "%")
        _correlation = self.format(self._correlation, "%")

        return f"corrupt {_ratio} {_correlation}"


@dataclass(eq=True, frozen=True)
class Rate(NetemCmd):
    """Netem Rate parameter class

    Class handles the generation of partial command for netem
    parameter - rate

    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
    """
    _rate: int = 0
    _overhead: int = 0

    def make_cmd(self):
        _rate = self.format(self._rate, "kbit")
        _overhead = self.format(self._overhead, "")

        return f"rate {_rate} {_overhead}"


class NetemCollection(TcCollection):
    """Class for composing and running the netem command

    All classes for netem 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()

        self._operation_check(operation)

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

        # generate empty cmd to reset the tc qdisc device
        if operation == "reset":
            _used_dtcls = set()
            operation = "change"

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