"""Objects for Bcaches."""

__all__ = ["Bcache", "Bcaches"]

from typing import Union

from . import ObjectField, ObjectFieldRelated, ObjectSet, ObjectType, to, check
from .nodes import Node
from .bcache_cache_sets import BcacheCacheSet
from .block_devices import BlockDevice
from .partitions import Partition
from .filesystem_groups import DeviceField, FilesystemGroup
from ..enum import CacheMode


class BcacheType(ObjectType):
    """Metaclass for `Bcache`."""

    async def read(cls, node, id):
        """Get `Bcache` by `id`."""
        if isinstance(node, str):
            system_id = node
        elif isinstance(node, Node):
            system_id = node.system_id
        else:
            raise TypeError("node must be a Node or str, not %s" % type(node).__name__)
        return cls(await cls._handler.read(system_id=system_id, id=id))


class Bcache(FilesystemGroup, metaclass=BcacheType):
    """A Bcache on a machine."""

    cache_mode = ObjectField.Checked("cache_mode", to(CacheMode), check(CacheMode))
    uuid = ObjectField.Checked("uuid", check(str), check(str))

    backing_device = DeviceField("backing_device")
    cache_set = ObjectFieldRelated("cache_set", "BcacheCacheSet", reverse=None)
    virtual_device = ObjectFieldRelated(
        "virtual_device", "BlockDevice", reverse=None, readonly=True
    )

    def __repr__(self):
        return super(Bcache, self).__repr__(
            fields={"name", "cache_mode", "size", "backing_device"}
        )

    async def delete(self):
        """Delete this Bcache."""
        await self._handler.delete(system_id=self.node.system_id, id=self.id)


class BcachesType(ObjectType):
    """Metaclass for `Bcaches`."""

    async def read(cls, node):
        """Get list of `Bcache`'s for `node`."""
        if isinstance(node, str):
            system_id = node
        elif isinstance(node, Node):
            system_id = node.system_id
        else:
            raise TypeError("node must be a Node or str, not %s" % type(node).__name__)
        data = await cls._handler.read(system_id=system_id)
        return cls(
            cls._object(item, local_data={"node_system_id": system_id}) for item in data
        )

    async def create(
        cls,
        node: Union[Node, str],
        name: str,
        backing_device: Union[BlockDevice, Partition],
        cache_set: Union[BcacheCacheSet, int],
        cache_mode: CacheMode,
        *,
        uuid: str = None
    ):
        """
        Create a Bcache on a Node.

        :param node: Node to create the interface on.
        :type node: `Node` or `str`
        :param name: Name of the Bcache.
        :type name: `str`
        :param backing_device: Either a block device or partition to create
            the Bcache from.
        :type backing_device: `BlockDevice` or `Partition`
        :param cache_set: Bcache cache set to use in front of backing device.
        :type cache_set: `BcacheCacheSet` or `int`
        :param cache_mode: Caching mode to use for this device.
        :type cache_mode: `CacheMode`
        :type backing_device: `BlockDevice` or `Partition`
        :param uuid: The UUID for the Bcache (optional).
        :type uuid: `str`
        """
        params = {"name": name}
        if isinstance(node, str):
            params["system_id"] = node
        elif isinstance(node, Node):
            params["system_id"] = node.system_id
        else:
            raise TypeError(
                "node must be a Node or str, not %s" % (type(node).__name__)
            )

        if isinstance(backing_device, BlockDevice):
            params["backing_device"] = backing_device.id
        elif isinstance(backing_device, Partition):
            params["backing_partition"] = backing_device.id
        else:
            raise TypeError(
                "backing_device must be a BlockDevice or Partition, "
                "not %s" % type(backing_device).__name__
            )

        if isinstance(cache_set, BcacheCacheSet):
            params["cache_set"] = cache_set.id
        elif isinstance(cache_set, int):
            params["cache_set"] = cache_set
        else:
            raise TypeError(
                "cache_set must be a BcacheCacheSet or int, "
                "not %s" % type(cache_set).__name__
            )

        if isinstance(cache_mode, CacheMode):
            params["cache_mode"] = cache_mode.value
        else:
            raise TypeError(
                "cache_mode must be a CacheMode, " "not %s" % type(cache_mode).__name__
            )

        if uuid is not None:
            params["uuid"] = uuid
        return cls._object(await cls._handler.create(**params))


class Bcaches(ObjectSet, metaclass=BcachesType):
    """The set of Bcaches on a machine."""

    @property
    def by_name(self):
        """Return mapping of name to `Bcache`."""
        return {bcache.name: bcache for bcache in self}

    def get_by_name(self, name):
        """Return a `Bcache` by its name."""
        return self.by_name[name]
