from .ui_node import UINode, UIBdevs, UILvolStores, UIVhosts
import rpc.client
import rpc
from functools import wraps


class UIRoot(UINode):
    """
    Root node for CLI menu tree structure. Refreshes running config on startup.
    """
    def __init__(self, s, shell):
        UINode.__init__(self, "/", shell=shell)
        self.current_bdevs = []
        self.current_lvol_stores = []
        self.current_vhost_ctrls = []
        self.set_rpc_target(s)
        self.verbose = False
        self.is_init = self.check_init()

    def refresh(self):
        if self.is_init is False:
            methods = self.get_rpc_methods(current=True)
            methods = "\n".join(methods)
            self.shell.log.warning("SPDK Application is not yet initialized.\n"
                                   "Please initialize subsystems with start_subsystem_init command.\n"
                                   "List of available commands in current state:\n"
                                   "%s" % methods)
        else:
            # Pass because we'd like to build main tree structure for "ls"
            # even if state is uninitialized
            pass

        self._children = set([])
        UIBdevs(self)
        UILvolStores(self)
        UIVhosts(self)

    def set_rpc_target(self, s):
        self.client = rpc.client.JSONRPCClient(s)

    def print_array(self, a):
        return " ".join(a)

    def verbose(f):
        # For any configuration calls (create, delete, construct, etc.)
        # Check if verbose option is to be used and set appropriately.
        # Do not use for "get_*" methods so that output is not
        # flooded.
        def w(self, **kwargs):
            self.client.verbose = self.verbose
            r = f(self, **kwargs)
            self.client.verbose = False
            return r
        return w

    def ui_command_start_subsystem_init(self):
        if rpc.start_subsystem_init(self.client):
            self.is_init = True
            self.refresh()

    def get_rpc_methods(self, current=False):
        return rpc.get_rpc_methods(self.client, current=current)

    def check_init(self):
        return "start_subsystem_init" not in self.get_rpc_methods(current=True)

    def get_bdevs(self, bdev_type):
        if self.is_init:
            self.current_bdevs = rpc.bdev.get_bdevs(self.client)
            # Following replace needs to be done in order for some of the bdev
            # listings to work: logical volumes, split disk.
            # For example logical volumes: listing in menu is "Logical_Volume"
            # (cannot have space), but the product name in SPDK is "Logical Volume"
            bdev_type = bdev_type.replace("_", " ")
            for bdev in [x for x in self.current_bdevs if bdev_type in x["product_name"].lower()]:
                test = Bdev(bdev)
                yield test

    def get_bdevs_iostat(self, **kwargs):
        return rpc.bdev.get_bdevs_iostat(self.client, **kwargs)

    @verbose
    def split_bdev(self, **kwargs):
        response = rpc.bdev.construct_split_vbdev(self.client, **kwargs)
        return self.print_array(response)

    @verbose
    def destruct_split_bdev(self, **kwargs):
        rpc.bdev.destruct_split_vbdev(self.client, **kwargs)

    @verbose
    def delete_bdev(self, name):
        rpc.bdev.delete_bdev(self.client, bdev_name=name)

    @verbose
    def create_malloc_bdev(self, **kwargs):
        response = rpc.bdev.construct_malloc_bdev(self.client, **kwargs)
        return response

    @verbose
    def delete_malloc_bdev(self, **kwargs):
        rpc.bdev.delete_malloc_bdev(self.client, **kwargs)

    @verbose
    def create_iscsi_bdev(self, **kwargs):
        response = rpc.bdev.construct_iscsi_bdev(self.client, **kwargs)
        return response

    @verbose
    def delete_iscsi_bdev(self, **kwargs):
        rpc.bdev.delete_iscsi_bdev(self.client, **kwargs)

    @verbose
    def create_aio_bdev(self, **kwargs):
        response = rpc.bdev.construct_aio_bdev(self.client, **kwargs)
        return response

    @verbose
    def delete_aio_bdev(self, **kwargs):
        rpc.bdev.delete_aio_bdev(self.client, **kwargs)

    @verbose
    def create_lvol_bdev(self, **kwargs):
        response = rpc.lvol.construct_lvol_bdev(self.client, **kwargs)
        return response

    @verbose
    def destroy_lvol_bdev(self, **kwargs):
        response = rpc.lvol.destroy_lvol_bdev(self.client, **kwargs)
        return response

    @verbose
    def create_nvme_bdev(self, **kwargs):
        response = rpc.bdev.construct_nvme_bdev(self.client, **kwargs)
        return response

    @verbose
    def delete_nvme_controller(self, **kwargs):
        rpc.bdev.delete_nvme_controller(self.client, **kwargs)

    @verbose
    def create_null_bdev(self, **kwargs):
        response = rpc.bdev.construct_null_bdev(self.client, **kwargs)
        return response

    @verbose
    def delete_null_bdev(self, **kwargs):
        rpc.bdev.delete_null_bdev(self.client, **kwargs)

    @verbose
    def create_error_bdev(self, **kwargs):
        response = rpc.bdev.construct_error_bdev(self.client, **kwargs)

    @verbose
    def delete_error_bdev(self, **kwargs):
        rpc.bdev.delete_error_bdev(self.client, **kwargs)

    def get_lvol_stores(self):
        if self.is_init:
            self.current_lvol_stores = rpc.lvol.get_lvol_stores(self.client)
            for lvs in self.current_lvol_stores:
                yield LvolStore(lvs)

    @verbose
    def create_lvol_store(self, **kwargs):
        response = rpc.lvol.construct_lvol_store(self.client, **kwargs)
        return response

    @verbose
    def delete_lvol_store(self, **kwargs):
        rpc.lvol.destroy_lvol_store(self.client, **kwargs)

    @verbose
    def create_pmem_pool(self, **kwargs):
        response = rpc.pmem.create_pmem_pool(self.client, **kwargs)
        return response

    @verbose
    def delete_pmem_pool(self, **kwargs):
        rpc.pmem.delete_pmem_pool(self.client, **kwargs)

    @verbose
    def create_pmem_bdev(self, **kwargs):
        response = rpc.bdev.construct_pmem_bdev(self.client, **kwargs)
        return response

    @verbose
    def delete_pmem_bdev(self, **kwargs):
        response = rpc.bdev.delete_pmem_bdev(self.client, **kwargs)
        return response

    @verbose
    def create_rbd_bdev(self, **kwargs):
        response = rpc.bdev.construct_rbd_bdev(self.client, **kwargs)
        return response

    @verbose
    def delete_rbd_bdev(self, **kwargs):
        response = rpc.bdev.delete_rbd_bdev(self.client, **kwargs)
        return response

    @verbose
    def create_virtio_dev(self, **kwargs):
        response = rpc.vhost.construct_virtio_dev(self.client, **kwargs)
        return self.print_array(response)

    @verbose
    def remove_virtio_bdev(self, **kwargs):
        response = rpc.vhost.remove_virtio_bdev(self.client, **kwargs)
        return response

    def get_virtio_scsi_devs(self):
        if self.is_init:
            for bdev in rpc.vhost.get_virtio_scsi_devs(self.client):
                test = Bdev(bdev)
                yield test

    def list_vhost_ctrls(self):
        if self.is_init:
            self.current_vhost_ctrls = rpc.vhost.get_vhost_controllers(self.client)

    def get_vhost_ctrlrs(self, ctrlr_type):
        if self.is_init:
            self.list_vhost_ctrls()
            for ctrlr in [x for x in self.current_vhost_ctrls if ctrlr_type in list(x["backend_specific"].keys())]:
                yield VhostCtrlr(ctrlr)

    @verbose
    def remove_vhost_controller(self, **kwargs):
        rpc.vhost.remove_vhost_controller(self.client, **kwargs)

    @verbose
    def create_vhost_scsi_controller(self, **kwargs):
        rpc.vhost.construct_vhost_scsi_controller(self.client, **kwargs)

    @verbose
    def create_vhost_blk_controller(self, **kwargs):
        rpc.vhost.construct_vhost_blk_controller(self.client, **kwargs)

    @verbose
    def remove_vhost_scsi_target(self, **kwargs):
        rpc.vhost.remove_vhost_scsi_target(self.client, **kwargs)

    @verbose
    def add_vhost_scsi_lun(self, **kwargs):
        rpc.vhost.add_vhost_scsi_lun(self.client, **kwargs)

    def set_vhost_controller_coalescing(self, **kwargs):
        rpc.vhost.set_vhost_controller_coalescing(self.client, **kwargs)


class Bdev(object):
    def __init__(self, bdev_info):
        """
        All class attributes are set based on what information is received
        from get_bdevs RPC call.
        # TODO: Document in docstring parameters which describe bdevs.
        # TODO: Possible improvement: JSON schema might be used here in future
        """
        for i in list(bdev_info.keys()):
            setattr(self, i, bdev_info[i])


class LvolStore(object):
    def __init__(self, lvs_info):
        """
        All class attributes are set based on what information is received
        from get_bdevs RPC call.
        # TODO: Document in docstring parameters which describe bdevs.
        # TODO: Possible improvement: JSON schema might be used here in future
        """
        for i in list(lvs_info.keys()):
            setattr(self, i, lvs_info[i])


class VhostCtrlr(object):
    def __init__(self, ctrlr_info):
        """
        All class attributes are set based on what information is received
        from get_vhost_controllers RPC call.
        # TODO: Document in docstring parameters which describe bdevs.
        # TODO: Possible improvement: JSON schema might be used here in future
        """
        for i in list(ctrlr_info.keys()):
            setattr(self, i, ctrlr_info[i])