import abc

from . import kinds
from .errors import XAirRemoteError
from .meta import bool_prop
from .util import _get_level_val, _set_level_val, lin_get, lin_set


class IConfig(abc.ABC):
    """Abstract Base Class for config"""

    def __init__(self, remote):
        self._remote = remote

    def getter(self, param: str):
        self._remote.send(f"{self.address}/{param}")
        return self._remote.info_response

    def setter(self, param: str, val: int):
        self._remote.send(f"{self.address}/{param}", val)

    @abc.abstractmethod
    def address(self):
        pass


class Config(IConfig):
    """Concrete class for config"""

    @classmethod
    def make(cls, remote):
        """
        Factory function for Config

        Returns a Config class of a kind.
        """
        LINKS_cls = _make_links_mixins[remote.kind.id_]
        MONITOR_cls = type(f"ConfigMonitor", (Config.Monitor, cls), {})
        CONFIG_cls = type(
            f"Config{remote.kind}",
            (cls, LINKS_cls),
            {"monitor": MONITOR_cls(remote)},
        )
        return CONFIG_cls(remote)

    @property
    def address(self) -> str:
        return f"/config"

    @property
    def amixenable(self) -> bool:
        return self.getter("mute")[0] == 1

    @amixenable.setter
    def amixenable(self, val: bool):
        if not isinstance(val, bool):
            raise XAirRemoteError("amixenable is a bool parameter")
        self.setter("amixenable", 1 if val else 0)

    @property
    def amixlock(self) -> bool:
        return self.getter("amixlock")[0] == 1

    @amixlock.setter
    def amixlock(self, val: bool):
        if not isinstance(val, bool):
            raise XAirRemoteError("amixlock is a bool parameter")
        self.setter("amixlock", 1 if val else 0)

    @property
    def mute_group(self) -> bool:
        return self.getter("mute")[0] == 1

    @mute_group.setter
    def mute_group(self, val: bool):
        if not isinstance(val, bool):
            raise XAirRemoteError("mute_group is a bool parameter")
        self.setter("mute", 1 if val else 0)

    class Monitor:
        @property
        def address(self) -> str:
            root = super(Config.Monitor, self).address
            return f"{root}/solo"

        @property
        def level(self) -> float:
            retval = self.getter("level")[0]
            return _get_level_val(retval)

        @level.setter
        def level(self, val: float):
            _set_level_val(self, val)

        @property
        def source(self) -> int:
            return int(self.getter("source")[0])

        @source.setter
        def source(self, val: int):
            if not isinstance(val, int):
                raise XAirRemoteError("source is an int parameter")
            self.setter(f"source", val)

        @property
        def sourcetrim(self) -> float:
            return round(lin_get(-18, 18, self.getter("sourcetrim")[0]), 1)

        @sourcetrim.setter
        def sourcetrim(self, val: float):
            if not isinstance(val, float):
                raise XAirRemoteError(
                    "sourcetrim is a float parameter, expected value in range -18 to 18"
                )
            self.setter("sourcetrim", lin_set(-18, 18, val))

        @property
        def chmode(self) -> bool:
            return self.getter("chmode")[0] == 1

        @chmode.setter
        def chmode(self, val: bool):
            if not isinstance(val, bool):
                raise XAirRemoteError("chmode is a bool parameter")
            self.setter("chmode", 1 if val else 0)

        @property
        def busmode(self) -> bool:
            return self.getter("busmode")[0] == 1

        @busmode.setter
        def busmode(self, val: bool):
            if not isinstance(val, bool):
                raise XAirRemoteError("busmode is a bool parameter")
            self.setter("busmode", 1 if val else 0)

        @property
        def dimgain(self) -> int:
            return int(lin_get(-40, 0, self.getter("dimatt")[0]))

        @dimgain.setter
        def dimgain(self, val: int):
            if not isinstance(val, int):
                raise XAirRemoteError(
                    "dimgain is an int parameter, expected value in range -40 to 0"
                )
            self.setter("dimatt", lin_set(-40, 0, val))

        @property
        def dim(self) -> bool:
            return self.getter("dim")[0] == 1

        @dim.setter
        def dim(self, val: bool):
            if not isinstance(val, bool):
                raise XAirRemoteError("dim is a bool parameter")
            self.setter("dim", 1 if val else 0)

        @property
        def mono(self) -> bool:
            return self.getter("mono")[0] == 1

        @mono.setter
        def mono(self, val: bool):
            if not isinstance(val, bool):
                raise XAirRemoteError("mono is a bool parameter")
            self.setter("mono", 1 if val else 0)

        @property
        def mute(self) -> bool:
            return self.getter("mute")[0] == 1

        @mute.setter
        def mute(self, val: bool):
            if not isinstance(val, bool):
                raise XAirRemoteError("mute is a bool parameter")
            self.setter("mute", 1 if val else 0)

        @property
        def dimfpl(self) -> bool:
            return self.getter("dimfpl")[0] == 1

        @dimfpl.setter
        def dimfpl(self, val: bool):
            if not isinstance(val, bool):
                raise XAirRemoteError("dimfpl is a bool parameter")
            self.setter("dimfpl", 1 if val else 0)


def _make_links_mixin(kind):
    """Creates a links mixin"""
    return type(
        f"Links{kind}",
        (),
        {
            "link_eq": bool_prop("linkcfg/eq"),
            "link_dyn": bool_prop("linkcfg/dyn"),
            "link_fader_mute": bool_prop("linkcfg/fdrmute"),
            **{
                f"chlink{i}_{i+1}": bool_prop(f"chlink/{i}-{i+1}")
                for i in range(1, kind.num_strip, 2)
            },
            **{
                f"buslink{i}_{i+1}": bool_prop(f"buslink/{i}-{i+1}")
                for i in range(1, kind.num_bus, 2)
            },
        },
    )


_make_links_mixins = {kind.id_: _make_links_mixin(kind) for kind in kinds.all}
