from typing import Union

from .errors import XAirRemoteError
from .meta import geq_prop
from .util import _get_fader_val, _set_fader_val, lin_get, lin_set, log_get, log_set

"""
Classes shared by /ch, /rtn, /rt/aux, /bus, /fxsend, /lr
"""


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

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

    @name.setter
    def name(self, val: str):
        if not isinstance(val, str):
            raise XAirRemoteError("name is a string parameter")
        self.setter("name", val)

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

    @property
    def highpassfilter(self) -> int:
        return int(log_get(20, 400, self.getter("hpf")[0]))

    @highpassfilter.setter
    def highpassfilter(self, val: int):
        if not isinstance(val, int):
            raise XAirRemoteError("highpassfilter is an int parameter")
        self.setter("hpf", log_set(20, 400, val))


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

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

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

    @property
    def mode(self) -> str:
        opts = ("gate", "exp2", "exp3", "exp4", "duck")
        return opts[self.getter("mode")[0]]

    @mode.setter
    def mode(self, val: str):
        opts = ("gate", "exp2", "exp3", "exp4", "duck")
        if not isinstance(val, str) and val not in opts:
            raise XAirRemoteError(f"mode is a string parameter, expected one of {opts}")
        self.setter("mode", opts.index(val))

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

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

    @property
    def range(self) -> int:
        return int(lin_get(3, 60, self.getter("range")[0]))

    @range.setter
    def range(self, val: int):
        if not isinstance(val, int):
            raise XAirRemoteError(
                "range is an int parameter, expected value in range 3 to 60"
            )
        self.setter("range", lin_set(3, 60, val))

    @property
    def attack(self) -> int:
        return int(lin_get(0, 120, self.getter("attack")[0]))

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

    @property
    def hold(self) -> Union[float, int]:
        val = log_get(0.02, 2000, self.getter("hold")[0])
        return round(val, 1) if val < 100 else int(val)

    @hold.setter
    def hold(self, val: float):
        self.setter("hold", log_set(0.02, 2000, val))

    @property
    def release(self) -> int:
        return int(log_get(5, 4000, self.getter("release")[0]))

    @release.setter
    def release(self, val: int):
        if not isinstance(val, int):
            raise XAirRemoteError(
                "release is an int parameter, expected value in range 5 to 4000"
            )
        self.setter("release", log_set(5, 4000, val))

    @property
    def keysource(self):
        return self.getter("keysrc")[0]

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

    @property
    def filteron(self):
        return self.getter("filter/on")[0] == 1

    @filteron.setter
    def filteron(self, val: bool):
        if not isinstance(val, bool):
            raise XAirRemoteError("filteron is a boolean parameter")
        self.setter("filter/on", 1 if val else 0)

    @property
    def filtertype(self) -> int:
        return int(self.getter("filter/type")[0])

    @filtertype.setter
    def filtertype(self, val: int):
        if not isinstance(val, int):
            raise XAirRemoteError("filtertype is an int parameter")
        self.setter("filter/type", val)

    @property
    def filterfreq(self) -> Union[float, int]:
        retval = log_get(20, 20000, self.getter("filter/f")[0])
        return int(retval) if retval > 1000 else round(retval, 1)

    @filterfreq.setter
    def filterfreq(self, val: Union[float, int]):
        self.setter("filter/f", log_set(20, 20000, val))


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

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

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

    @property
    def mode(self) -> str:
        opts = ("comp", "exp")
        return opts[self.getter("mode")[0]]

    @mode.setter
    def mode(self, val: str):
        opts = ("comp", "exp")
        if not isinstance(val, str) and val not in opts:
            raise XAirRemoteError(f"mode is a string parameter, expected one of {opts}")
        self.setter("mode", opts.index(val))

    @property
    def det(self) -> str:
        opts = ("peak", "rms")
        return opts[self.getter("det")[0]]

    @det.setter
    def det(self, val: str):
        opts = ("peak", "rms")
        if not isinstance(val, str) and val not in opts:
            raise XAirRemoteError(f"det is a string parameter, expected one of {opts}")
        self.setter("det", opts.index(val))

    @property
    def env(self) -> str:
        opts = ("lin", "log")
        return opts[self.getter("env")[0]]

    @env.setter
    def env(self, val: str):
        opts = ("lin", "log")
        if not isinstance(val, str) and val not in opts:
            raise XAirRemoteError(f"env is a string parameter, expected one of {opts}")
        self.setter("env", opts.index(val))

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

    @threshold.setter
    def threshold(self, val: float):
        if not isinstance(val, float):
            raise XAirRemoteError(
                "threshold is a float parameter, expected value in range -80 to 0"
            )
        self.setter("thr", lin_set(-60, 0, val))

    @property
    def ratio(self) -> Union[float, int]:
        opts = (1.1, 1.3, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0, 7.0, 10, 20, 100)
        return opts[self.getter("ratio")[0]]

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

    @property
    def knee(self) -> int:
        return int(lin_get(0, 5, self.getter("knee")[0]))

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

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

    @mgain.setter
    def mgain(self, val: float):
        self.setter("mgain", lin_set(0, 24, val))

    @property
    def attack(self) -> int:
        return int(lin_get(0, 120, self.getter("attack")[0]))

    @attack.setter
    def attack(self, val: int):
        self.setter("attack", lin_set(0, 120, val))

    @property
    def hold(self) -> Union[float, int]:
        val = log_get(0.02, 2000, self.getter("hold")[0])
        return round(val, 1) if val < 100 else int(val)

    @hold.setter
    def hold(self, val: float):
        self.setter("hold", log_set(0.02, 2000, val))

    @property
    def release(self) -> int:
        return int(log_get(5, 4000, self.getter("release")[0]))

    @release.setter
    def release(self, val: int):
        if not isinstance(val, int):
            raise XAirRemoteError(
                "release is an int parameter, expected value in range 5 to 4000"
            )
        self.setter("release", log_set(5, 4000, val))

    @property
    def mix(self) -> int:
        return int(lin_get(0, 100, self.getter("mix")[0]))

    @mix.setter
    def mix(self, val: int):
        if not isinstance(val, int):
            raise XAirRemoteError(
                "mix is an int parameter, expected value in range 0 to 5"
            )
        self.setter("mix", lin_set(0, 100, val))

    @property
    def keysource(self):
        return self.getter("keysrc")[0]

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

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

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

    @property
    def filteron(self):
        return self.getter("filter/on")[0] == 1

    @filteron.setter
    def filteron(self, val: bool):
        if not isinstance(val, bool):
            raise XAirRemoteError("filteron is a boolean parameter")
        self.setter("filter/on", 1 if val else 0)

    @property
    def filtertype(self) -> int:
        return int(self.getter("filter/type")[0])

    @filtertype.setter
    def filtertype(self, val: int):
        if not isinstance(val, int):
            raise XAirRemoteError("filtertype is an int parameter")
        self.setter("filter/type", val)

    @property
    def filterfreq(self) -> Union[float, int]:
        retval = log_get(20, 20000, self.getter("filter/f")[0])
        return int(retval) if retval > 1000 else round(retval, 1)

    @filterfreq.setter
    def filterfreq(self, val: Union[float, int]):
        self.setter("filter/f", log_set(20, 20000, val))


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

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

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

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

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


class EQ:
    @classmethod
    def make_fourband(cls, _cls, remote, index=None):
        EQBand_cls = type("EQBand", (EQ.EQBand, _cls), {})
        return type(
            "EQ",
            (cls,),
            {
                "low": EQBand_cls(1, remote, index),
                "lomid": EQBand_cls(2, remote, index),
                "himid": EQBand_cls(3, remote, index),
                "high": EQBand_cls(4, remote, index),
            },
        )

    @classmethod
    def make_sixband(cls, _cls, remote, index=None):
        EQBand_cls = type("EQBand", (EQ.EQBand, _cls), {})
        return type(
            "EQ",
            (cls,),
            {
                "low": EQBand_cls(1, remote, index),
                "low2": EQBand_cls(2, remote, index),
                "lomid": EQBand_cls(3, remote, index),
                "himid": EQBand_cls(4, remote, index),
                "high2": EQBand_cls(5, remote, index),
                "high": EQBand_cls(6, remote, index),
            },
        )

    @property
    def address(self) -> str:
        root = super(EQ, self).address
        return f"{root}/eq"

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

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

    @property
    def mode(self) -> str:
        opts = ("peq", "geq", "teq")
        return opts[self.getter("mode")[0]]

    @mode.setter
    def mode(self, val: str):
        opts = ("peq", "geq", "teq")
        if not isinstance(val, str) and val not in opts:
            raise XAirRemoteError(f"mode is a string parameter, expected one of {opts}")
        self.setter("mode", opts.index(val))

    class EQBand:
        def __init__(self, i, remote, index):
            if index is None:
                super(EQ.EQBand, self).__init__(remote)
            else:
                super(EQ.EQBand, self).__init__(remote, index)
            self.i = i

        @property
        def address(self) -> str:
            root = super(EQ.EQBand, self).address
            return f"{root}/eq/{self.i}"

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

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

        @property
        def frequency(self) -> float:
            retval = log_get(20, 20000, self.getter("f")[0])
            return round(retval, 1)

        @frequency.setter
        def frequency(self, val: float):
            self.setter("f", log_set(20, 20000, val))

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

        @gain.setter
        def gain(self, val: float):
            self.setter("g", lin_set(-15, 15, val))

        @property
        def quality(self) -> float:
            retval = log_get(0.3, 10, self.getter("q")[0])
            return round(retval, 1)

        @quality.setter
        def quality(self, val: float):
            self.setter("q", log_set(0.3, 10, val))


class GEQ:
    @classmethod
    def make(cls):
        # fmt: off
        return type(
            "GEQ",
            (cls,),
            {
                **{
                    f"slider_{param}": geq_prop(param)
                    for param in [
                        "20", "25", "31_5", "40", "50", "63", "80", "100", "125",
                        "160", "200", "250", "315" "400", "500", "630", "800", "1k",
                        "1k25", "1k6", "2k", "2k5", "3k15", "4k", "5k", "6k3", "8k",
                        "10k", "12k5", "16k", "20k",
                    ]
                }
            },
        )
        # fmt: on

    @property
    def address(self) -> str:
        root = super(GEQ, self).address
        return f"{root}/geq"


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

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

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

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

    @fader.setter
    def fader(self, val: float):
        _set_fader_val(self, val)

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

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


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

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

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

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

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


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

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

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

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

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