from typing import Optional

from .iremote import IRemote
from .kinds import kinds_all


class FX(IRemote):
    def __str__(self):
        return f"{type(self).__name__}"

    @property
    def identifier(self) -> str:
        return "FX"

    @property
    def reverb(self) -> bool:
        return self.getter("reverb.On") == 1

    @reverb.setter
    def reverb(self, val: bool):
        self.setter("reverb.On", 1 if val else 0)

    @property
    def reverb_ab(self) -> bool:
        return self.getter("reverb.ab") == 1

    @reverb_ab.setter
    def reverb_ab(self, val: bool):
        self.setter("reverb.ab", 1 if val else 0)

    @property
    def delay(self) -> bool:
        return self.getter("delay.On") == 1

    @delay.setter
    def delay(self, val: bool):
        self.setter("delay.On", 1 if val else 0)

    @property
    def delay_ab(self) -> bool:
        return self.getter("delay.ab") == 1

    @delay_ab.setter
    def delay_ab(self, val: bool):
        self.setter("delay.ab", 1 if val else 0)


class Patch(IRemote):
    @classmethod
    def make(cls, remote):
        """
        Factory method for Patch.

        Mixes in required classes.

        Returns a Patch class of a kind.
        """
        ASIO_cls = _make_asio_mixins(remote)[remote.kind.name]
        return type(
            f"Patch{remote.kind}",
            (cls, ASIO_cls),
            {
                "composite": tuple(Composite(remote, i) for i in range(8)),
                "insert": tuple(Insert(remote, i) for i in range(remote.kind.insert)),
            },
        )(remote)

    def __str__(self):
        return f"{type(self).__name__}"

    @property
    def identifier(self) -> str:
        return "patch"

    @property
    def postfadercomp(self) -> bool:
        return self.getter("postfadercomposite") == 1

    @postfadercomp.setter
    def postfadercomp(self, val: bool):
        self.setter("postfadercomposite", 1 if val else 0)

    @property
    def postfxinsert(self) -> bool:
        return self.getter("postfxinsert") == 1

    @postfxinsert.setter
    def postfxinsert(self, val: bool):
        self.setter("postfxinsert", 1 if val else 0)


class Asio(IRemote):
    @property
    def identifier(self) -> str:
        return "patch"


class AsioIn(Asio):
    def get(self) -> int:
        return int(self.getter(f"asio[{self.index}]"))

    def set(self, val: int):
        self.setter(f"asio[{self.index}]", val)


class AsioOut(Asio):
    def __init__(self, remote, i, param):
        IRemote.__init__(self, remote, i)
        self._param = param

    def get(self) -> int:
        return int(self.getter(f"out{self._param}[{self.index}]"))

    def set(self, val: int):
        self.setter(f"out{self._param}[{self.index}]", val)


def _make_asio_mixin(remote, kind):
    """Creates an ASIO mixin for a kind"""
    asio_in, asio_out = kind.asio

    return type(
        f"ASIO{kind}",
        (IRemote,),
        {
            "asio": tuple(AsioIn(remote, i) for i in range(asio_in)),
            **{
                param: tuple(AsioOut(remote, i, param) for i in range(asio_out))
                for param in ["A2", "A3", "A4", "A5"]
            },
        },
    )


def _make_asio_mixins(remote):
    return {kind.name: _make_asio_mixin(remote, kind) for kind in kinds_all}


class Composite(IRemote):
    @property
    def identifier(self) -> str:
        return "patch"

    def get(self) -> int:
        return int(self.getter(f"composite[{self.index}]"))

    def set(self, val: int):
        self.setter(f"composite[{self.index}]", val)


class Insert(IRemote):
    @property
    def identifier(self) -> str:
        return "patch"

    @property
    def on(self) -> bool:
        return self.getter(f"insert[{self.index}]") == 1

    @on.setter
    def on(self, val: bool):
        self.setter(f"insert[{self.index}]", 1 if val else 0)


class Option(IRemote):
    @classmethod
    def make(cls, remote):
        """
        Factory method for Option.

        Mixes in required classes.

        Returns a Option class of a kind.
        """
        return type(
            f"Option{remote.kind}",
            (cls,),
            {
                "delay": tuple(Delay(remote, i) for i in range(remote.kind.phys_out)),
            },
        )(remote)

    def __str__(self):
        return f"{type(self).__name__}"

    @property
    def identifier(self) -> str:
        return "option"

    @property
    def sr(self) -> int:
        return int(self.getter("sr"))

    @sr.setter
    def sr(self, val: int):
        opts = (44100, 48000, 88200, 96000, 176400, 192000)
        if val not in opts:
            self.logger.warning(f"sr got: {val} but expected a value in {opts}")
        self.setter("sr", val)

    @property
    def asiosr(self) -> bool:
        return self.getter("asiosr") == 1

    @asiosr.setter
    def asiosr(self, val: bool):
        self.setter("asiosr", 1 if val else 0)

    @property
    def monitoronsel(self) -> bool:
        return self.getter("monitoronsel") == 1

    @monitoronsel.setter
    def monitoronsel(self, val: bool):
        self.setter("monitoronsel", 1 if val else 0)

    def buffer(self, driver, buffer):
        self.setter(f"buffer.{driver}", buffer)


class Delay(IRemote):
    @property
    def identifier(self) -> str:
        return "option"

    def get(self) -> int:
        return int(self.getter(f"delay[{self.index}]"))

    def set(self, val: int):
        self.setter(f"delay[{self.index}]", val)


class Midi:
    def __init__(self):
        self._channel = None
        self.cache = {}
        self._most_recent = None

    @property
    def channel(self) -> int:
        return self._channel

    @property
    def current(self) -> int:
        return self._most_recent

    def get(self, key: int) -> Optional[int]:
        return self.cache.get(key)

    def _set(self, key: int, velocity: int):
        self.cache[key] = velocity


class VmGui:
    _launched = None

    @property
    def launched(self) -> bool:
        return self._launched

    @launched.setter
    def launched(self, val: bool):
        self._launched = val

    @property
    def launched_by_api(self):
        return not self.launched