'''
Implements the targetcli target related UI.

This file is part of targetcli.
Copyright (c) 2011-2013 by Datera, Inc

Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
'''

from ui_node import UINode, UIRTSLibNode
from ui_backstore import dedup_so_name
from rtslib import RTSLibError, RTSLibBrokenLink, utils
from rtslib import NodeACL, NetworkPortal, MappedLUN
from rtslib import Target, TPG, LUN

class UIFabricModule(UIRTSLibNode):
    '''
    A fabric module UI.
    '''
    def __init__(self, fabric_module, parent):
        UIRTSLibNode.__init__(self, fabric_module.name, fabric_module, parent)
        self.cfs_cwd = fabric_module.path
        self.refresh()
        if self.rtsnode.has_feature('discovery_auth'):
            for param in ['userid', 'password',
                          'mutual_userid', 'mutual_password',
                          'enable']:
                self.define_config_group_param('discovery_auth',
                                               param, 'string')
        self.refresh()

    def ui_getgroup_discovery_auth(self, auth_attr):
        '''
        This is the backend method for getting discovery_auth attributes.
        @param auth_attr: The auth attribute to get the value of.
        @type auth_attr: str
        @return: The auth attribute's value
        @rtype: str
        '''
        value = None
        if auth_attr == 'password':
            value = self.rtsnode.discovery_password
        elif auth_attr == 'userid':
            value = self.rtsnode.discovery_userid
        elif auth_attr == 'mutual_password':
            value = self.rtsnode.discovery_mutual_password
        elif auth_attr == 'mutual_userid':
            value = self.rtsnode.discovery_mutual_userid
        elif auth_attr == 'enable':
            value = self.rtsnode.discovery_enable_auth
        return value

    def ui_setgroup_discovery_auth(self, auth_attr, value):
        '''
        This is the backend method for setting discovery auth attributes.
        @param auth_attr: The auth attribute to set the value of.
        @type auth_attr: str
        @param value: The auth's value
        @type value: str
        '''
        self.assert_root()
        if value is None:
            value = ''
        if auth_attr == 'password':
            self.rtsnode.discovery_password = value
        elif auth_attr == 'userid':
            self.rtsnode.discovery_userid = value
        elif auth_attr == 'mutual_password':
            self.rtsnode.discovery_mutual_password = value
        elif auth_attr == 'mutual_userid':
            self.rtsnode.discovery_mutual_userid = value
        elif auth_attr == 'enable':
            self.rtsnode.discovery_enable_auth = value

    def refresh(self):
        self._children = set([])
        for target in self.rtsnode.targets:
            self.shell.log.debug("Found target %s under fabric module %s."
                                 % (target.wwn, target.fabric_module))
            if target.has_feature('tpgts'):
                UIMultiTPGTarget(target, self)
            else:
                UITarget(target, self)

    def summary(self):
        no_targets = len(self._children)
        if no_targets != 1:
            msg = "%d Targets" % no_targets
        else:
            msg = "%d Target" % no_targets
        return (msg, None)

    def ui_command_create(self, wwn=None):
        '''
        Creates a new target. The I{wwn} format depends on the transport(s)
        supported by the fabric module. If the I{wwn} is ommited, then a
        target will be created using either a randomly generated WWN of the
        proper type, or the first unused WWN in the list of possible WWNs if
        one is available. If WWNs are constrained to a list (i.e. for hardware
        targets addresses) and all WWNs are in use, the target creation will
        fail. Use the B{info} command to get more information abour WWN type
        and possible values.

        SEE ALSO
        ========
        B{info}
        '''
        self.assert_root()
        target = Target(self.rtsnode, wwn, mode='create')
        wwn = target.wwn
        if target.has_feature('tpgts'):
            ui_target = UIMultiTPGTarget(target, self)
            self.shell.log.info("Created target %s." % wwn)
            return ui_target.ui_command_create()
        else:
            ui_target = UITarget(target, self)
            self.shell.log.info("Created target %s." % wwn)
            return self.new_node(ui_target)

    def ui_complete_create(self, parameters, text, current_param):
        '''
        Parameter auto-completion method for user command create.
        @param parameters: Parameters on the command line.
        @type parameters: dict
        @param text: Current text of parameter being typed by the user.
        @type text: str
        @param current_param: Name of parameter to complete.
        @type current_param: str
        @return: Possible completions
        @rtype: list of str
        '''
        spec = self.rtsnode.spec
        if current_param == 'wwn' and spec['wwn_list'] is not None:
            existing_wwns = [child.wwn for child in self.rtsnode.targets]
            completions = [wwn for wwn in spec['wwn_list']
                           if wwn.startswith(text)
                           if wwn not in existing_wwns]
        else:
            completions = []

        if len(completions) == 1:
            return [completions[0] + ' ']
        else:
            return completions

    def ui_command_delete(self, wwn):
        '''
        Recursively deletes the target with the specified I{wwn}, and all
        objects hanging under it.

        SEE ALSO
        ========
        B{create}
        '''
        self.assert_root()
        target = Target(self.rtsnode, wwn, mode='lookup')
        target.delete()
        self.shell.log.info("Deleted Target %s." % wwn)
        self.refresh()

    def ui_complete_delete(self, parameters, text, current_param):
        '''
        Parameter auto-completion method for user command delete.
        @param parameters: Parameters on the command line.
        @type parameters: dict
        @param text: Current text of parameter being typed by the user.
        @type text: str
        @param current_param: Name of parameter to complete.
        @type current_param: str
        @return: Possible completions
        @rtype: list of str
        '''
        if current_param == 'wwn':
            wwns = [child.name for child in self.children]
            completions = [wwn for wwn in wwns if wwn.startswith(text)]
        else:
            completions = []

        if len(completions) == 1:
            return [completions[0] + ' ']
        else:
            return completions

    def ui_command_info(self):
        '''
        Displays information about the fabric module, notably the supported
        transports(s) and accepted B{wwn} format(s), as long as supported
        features.
        '''
        spec = self.rtsnode.spec
        self.shell.log.info("Fabric module name: %s" % self.name)
        self.shell.log.info("ConfigFS path: %s" % self.rtsnode.path)
        if spec['wwn_list'] is not None:
            self.shell.log.info("Allowed WWNs list (%s type): %s"
                                % (spec['wwn_type'],
                                   ', '.join(spec['wwn_list'])))
        else:
            self.shell.log.info("Supported WWN type: %s" % spec['wwn_type'])

        self.shell.log.info("Fabric module specfile: %s"
                            % self.rtsnode.spec_file)
        self.shell.log.info("Fabric module features: %s"
                            % ', '.join(spec['features']))
        self.shell.log.info("Corresponding kernel module: %s"
                            % spec['kernel_module'])

    def ui_command_version(self):
        '''
        Displays the target fabric module version.
        '''
        version = "Target fabric module %s: %s" \
                % (self.rtsnode.name, self.rtsnode.version)
        self.shell.con.display(version.strip())


class UIMultiTPGTarget(UIRTSLibNode):
    '''
    A generic target UI that has multiple TPGs.
    '''
    def __init__(self, target, parent):
        UIRTSLibNode.__init__(self, target.wwn, target, parent)
        self.cfs_cwd = target.path
        self.refresh()

    def refresh(self):
        self._children = set([])
        for tpg in self.rtsnode.tpgs:
            UITPG(tpg, self)

    def summary(self):
        if not self.rtsnode.fabric_module.is_valid_wwn(self.rtsnode.wwn):
            description = "INVALID WWN"
            is_healthy = False
        else:
            is_healthy = None
            no_tpgs = len(self._children)
            if no_tpgs != 1:
                description = "%d TPGs" % no_tpgs
            else:
                description = "%d TPG" % no_tpgs

        return (description, is_healthy)

    def ui_command_create(self, tag=None):
        '''
        Creates a new Target Portal Group within the target. The I{tag} must be
        a strictly positive integer value. If omitted, the next available
        Target Portal Group Tag (TPGT) will be used.

        SEE ALSO
        ========
        B{delete}
        '''
        self.assert_root()
        if tag is None:
            tags = [tpg.tag for tpg in self.rtsnode.tpgs]
            for index in range(1048576):
                if index not in tags and index > 0:
                    tag = index
                    break
            if tag is None:
                self.shell.log.error("Cannot find an available TPG Tag.")
                return
            else:
                self.shell.log.info("Selected TPG Tag %d." % tag)
        else:
            try:
                tag = int(tag)
            except ValueError:
                self.shell.log.error("The TPG Tag must be an integer value.")
                return
            else:
                if tag < 1:
                    self.shell.log.error("The TPG Tag must be >0.")
                    return

        tpg = TPG(self.rtsnode, tag, mode='create')
        if self.shell.prefs['auto_enable_tpgt']:
            tpg.enable = True
        self.shell.log.info("Successfully created TPG %s." % tpg.tag)
        ui_tpg = UITPG(tpg, self)
        return self.new_node(ui_tpg)

    def ui_command_delete(self, tag):
        '''
        Deletes the Target Portal Group with TPGT I{tag} from the target. The
        I{tag} must be a positive integer matching an existing TPGT.

        SEE ALSO
        ========
        B{create}
        '''
        self.assert_root()
        tpg = TPG(self.rtsnode, tag, mode='lookup')
        tpg.delete()
        self.shell.log.info("Deleted TPGT %s." % tag)
        self.refresh()

    def ui_complete_delete(self, parameters, text, current_param):
        '''
        Parameter auto-completion method for user command delete.
        @param parameters: Parameters on the command line.
        @type parameters: dict
        @param text: Current text of parameter being typed by the user.
        @type text: str
        @param current_param: Name of parameter to complete.
        @type current_param: str
        @return: Possible completions
        @rtype: list of str
        '''
        if current_param == 'tag':
            tags = [child.name[4:] for child in self.children]
            completions = [tag for tag in tags if tag.startswith(text)]
        else:
            completions = []

        if len(completions) == 1:
            return [completions[0] + ' ']
        else:
            return completions


class UITPG(UIRTSLibNode):
    '''
    A generic TPG UI.
    '''
    def __init__(self, tpg, parent):
        name = "tpgt%d" % tpg.tag
        UIRTSLibNode.__init__(self, name, tpg, parent)
        self.cfs_cwd = tpg.path
        self.refresh()

        UILUNs(tpg, self)

        if tpg.has_feature('acls'):
            UINodeACLs(self.rtsnode, self)
        if tpg.has_feature('nps'):
            UIPortals(self.rtsnode, self)

    def summary(self):
        if self.rtsnode.has_feature('nexus'):
            description = "%s" % self.rtsnode.nexus
        elif self.rtsnode.enable:
            description = "enabled"
        else:
            description = "disabled"
        return (description, True)

    def ui_command_enable(self):
        '''
        Enables the TPG.

        SEE ALSO
        ========
        B{disable status}
        '''
        self.assert_root()
        if self.rtsnode.enable:
            self.shell.log.info("The TPGT is already enabled.")
        else:
            self.rtsnode.enable = True
            self.shell.log.info("The TPGT has been enabled.")

    def ui_command_disable(self):
        '''
        Disables the TPG.

        SEE ALSO
        ========
        B{enable status}
        '''
        self.assert_root()
        if self.rtsnode.enable:
            self.rtsnode.enable = False
            self.shell.log.info("The TPGT has been disabled.")
        else:
            self.shell.log.info("The TPGT is already disabled.")


class UITarget(UITPG):
    '''
    A generic target UI merged with its only TPG.
    '''
    def __init__(self, target, parent):
        UITPG.__init__(self, TPG(target, 1), parent)
        self._name = target.wwn
        self.target = target
        self.rtsnode.enable = True

    def summary(self):
        if not self.target.fabric_module.is_valid_wwn(self.target.wwn):
            return ("INVALID WWN", False)
        else:
            return UITPG.summary(self)


class UINodeACLs(UINode):
    '''
    A generic UI for node ACLs.
    '''
    def __init__(self, tpg, parent):
        UINode.__init__(self, "acls", parent)
        self.tpg = tpg
        self.cfs_cwd = "%s/acls" % tpg.path
        self.refresh()

    def refresh(self):
        self._children = set([])
        for node_acl in self.tpg.node_acls:
            UINodeACL(node_acl, self)

    def summary(self):
        no_acls = len(self._children)
        if no_acls != 1:
            msg = "%d ACLs" % no_acls
        else:
            msg = "%d ACL" % no_acls
        return (msg, None)

    def ui_command_create(self, wwn, add_mapped_luns=None):
        '''
        Creates a Node ACL for the initiator node with the specified I{wwn}.
        The node's I{wwn} must match the expected WWN Type of the target's
        fabric module.

        If I{add_mapped_luns} is omitted, the global parameter
        B{auto_add_mapped_luns} will be used, else B{true} or B{false} are
        accepted. If B{true}, then after creating the ACL, mapped LUNs will be
        automatically created for all existing LUNs.

        SEE ALSO
        ========
        B{delete}
        '''
        self.assert_root()
        spec = self.tpg.parent_target.fabric_module.spec
        if not utils.is_valid_wwn(spec['wwn_type'], wwn):
            self.shell.log.error("'%s' is not a valid %s WWN."
                                 % (wwn, spec['wwn_type']))
            return

        add_mapped_luns = \
                self.ui_eval_param(add_mapped_luns, 'bool',
                                   self.shell.prefs['auto_add_mapped_luns'])

        try:
            node_acl = NodeACL(self.tpg, wwn, mode="create")
        except RTSLibError, msg:
            self.shell.log.error(msg)
            return
        else:
            self.shell.log.info("Successfully created Node ACL for %s"
                                % node_acl.node_wwn)
            ui_node_acl = UINodeACL(node_acl, self)

        if add_mapped_luns:
            for lun in self.tpg.luns:
                MappedLUN(node_acl, lun.lun, lun.lun, write_protect=False)
                self.shell.log.info("Created mapped LUN %d." % lun.lun)
            self.refresh()

        return self.new_node(ui_node_acl)

    def ui_command_delete(self, wwn):
        '''
        Deletes the Node ACL with the specified I{wwn}.

        SEE ALSO
        ========
        B{create}
        '''
        self.assert_root()
        node_acl = NodeACL(self.tpg, wwn, mode='lookup')
        node_acl.delete()
        self.shell.log.info("Successfully deleted Node ACL %s." % wwn)
        self.refresh()

    def ui_complete_delete(self, parameters, text, current_param):
        '''
        Parameter auto-completion method for user command delete.
        @param parameters: Parameters on the command line.
        @type parameters: dict
        @param text: Current text of parameter being typed by the user.
        @type text: str
        @param current_param: Name of parameter to complete.
        @type current_param: str
        @return: Possible completions
        @rtype: list of str
        '''
        if current_param == 'wwn':
            wwns = [acl.node_wwn for acl in self.tpg.node_acls]
            completions = [wwn for wwn in wwns if wwn.startswith(text)]
        else:
            completions = []

        if len(completions) == 1:
            return [completions[0] + ' ']
        else:
            return completions


class UINodeACL(UIRTSLibNode):
    '''
    A generic UI for a node ACL.
    '''
    def __init__(self, node_acl, parent):
        UIRTSLibNode.__init__(self, node_acl.node_wwn, node_acl, parent)
        self.define_config_group_param(
            'attribute', 'tcq_depth', 'string', "Command queue depth.", True)
        self.cfs_cwd = node_acl.path
        self.refresh()

    def ui_getgroup_attribute(self, attribute):
        '''
        This is the backend method for getting attributes.
        @param attribute: The attribute to get the value of.
        @type attribute: str
        @return: The attribute's value
        @rtype: arbitrary
        '''
        if attribute == 'tcq_depth':
            return self.rtsnode.tcq_depth
        else:
            return self.rtsnode.get_attribute(attribute)

    def ui_setgroup_attribute(self, attribute, value):
        '''
        This is the backend method for setting attributes.
        @param attribute: The attribute to set the value of.
        @type attribute: str
        @param value: The attribute's value
        @type value: arbitrary
        '''
        self.assert_root()
        if attribute == 'tcq_depth':
            self.rtsnode.tcq_depth = value
        else:
            self.rtsnode.set_attribute(attribute, value)

    def refresh(self):
        self._children = set([])
        for mlun in self.rtsnode.mapped_luns:
            UIMappedLUN(mlun, self)

    def summary(self):
        no_mluns = len(self._children)
        if no_mluns != 1:
            msg = "%d Mapped LUNs" % no_mluns
        else:
            msg = "%d Mapped LUN" % no_mluns
        return (msg, None)

    def ui_command_create(self, mapped_lun, tpg_lun, write_protect=None):
        '''
        Creates a mapping to one of the TPG LUNs for the initiator referenced
        by the ACL. The provided I{tpg_lun} will appear to that initiator as
        LUN I{mapped_lun}. If the I{write_protect} flag is set to B{1}, the
        initiator will not have write access to the Mapped LUN.

        SEE ALSO
        ========
        B{delete}
        '''
        self.assert_root()
        try:
            tpg_lun = int(tpg_lun)
            mapped_lun = int(mapped_lun)
        except ValueError:
            self.shell.log.error("Incorrect LUN value.")
            return

        mlun = MappedLUN(self.rtsnode, mapped_lun, tpg_lun, write_protect)
        ui_mlun = UIMappedLUN(mlun, self)
        self.shell.log.info("Created Mapped LUN %s." % mlun.mapped_lun)
        return self.new_node(ui_mlun)

    def ui_command_delete(self, mapped_lun):
        '''
        Deletes the specified I{mapped_lun}.

        SEE ALSO
        ========
        B{create}
        '''
        self.assert_root()
        mlun = MappedLUN(self.rtsnode, mapped_lun)
        mlun.delete()
        self.shell.log.info("Deleted Mapped LUN %s." % mapped_lun)
        self.refresh()

    def ui_complete_delete(self, parameters, text, current_param):
        '''
        Parameter auto-completion method for user command delete.
        @param parameters: Parameters on the command line.
        @type parameters: dict
        @param text: Current text of parameter being typed by the user.
        @type text: str
        @param current_param: Name of parameter to complete.
        @type current_param: str
        @return: Possible completions
        @rtype: list of str
        '''
        if current_param == 'mapped_lun':
            mluns = [str(mlun.mapped_lun) for mlun in self.rtsnode.mapped_luns]
            completions = [mlun for mlun in mluns if mlun.startswith(text)]
        else:
            completions = []

        if len(completions) == 1:
            return [completions[0] + ' ']
        else:
            return completions


class UIMappedLUN(UIRTSLibNode):
    '''
    A generic UI for MappedLUN objects.
    '''
    def __init__(self, mapped_lun, parent):
        name = "mapped_lun%d" % mapped_lun.mapped_lun
        UIRTSLibNode.__init__(self, name, mapped_lun, parent)
        self.cfs_cwd = mapped_lun.path
        self.refresh()

    def summary(self):
        mapped_lun = self.rtsnode
        is_healthy = True
        try:
            tpg_lun = mapped_lun.tpg_lun
        except RTSLibBrokenLink:
            description = "BROKEN LUN LINK"
            is_healthy = False
        else:
            if mapped_lun.write_protect:
                access_mode = 'ro'
            else:
                access_mode = 'rw'
            description = "lun%d (%s)" % (tpg_lun.lun, access_mode)

        return (description, is_healthy)


class UILUNs(UINode):
    '''
    A generic UI for TPG LUNs.
    '''
    def __init__(self, tpg, parent):
        UINode.__init__(self, "luns", parent)
        self.cfs_cwd = "%s/lun" % tpg.path
        self.tpg = tpg
        self.refresh()

    def refresh(self):
        self._children = set([])
        for lun in self.tpg.luns:
            UILUN(lun, self)

    def summary(self):
        no_luns = len(self._children)
        if no_luns != 1:
            msg = "%d LUNs" % no_luns
        else:
            msg = "%d LUN" % no_luns
        return (msg, None)

    def ui_command_create(self, storage_object, lun=None,
                          add_mapped_luns=None):
        '''
        Creates a new LUN in the Target Portal Group, attached to a storage
        object. If the I{lun} parameter is omitted, the first available LUN in
        the TPG will be used. If present, it must be a number greater than 0.
        Alternatively, the syntax I{lunX} where I{X} is a positive number is
        also accepted.
        
        The I{storage_object} must be the path of an existing storage object,
        i.e. B{/backstore/pscsi0/mydisk} to reference the B{mydisk} storage
        object of the virtual HBA B{pscsi0}.

        If I{add_mapped_luns} is omitted, the global parameter
        B{auto_add_mapped_luns} will be used, else B{true} or B{false} are
        accepted. If B{true}, then after creating the LUN, mapped LUNs will be
        automatically created for all existing node ACLs, mapping the new LUN.

        SEE ALSO
        ========
        B{delete}
        '''
        self.assert_root()
        if lun is None:
            luns = [lun.lun for lun in self.tpg.luns]
            for index in range(1048576):
                if index not in luns:
                    lun = index
                    break
            if lun is None:
                self.shell.log.error("Cannot find an available LUN.")
                return
            else:
                self.shell.log.info("Selected LUN %d." % lun)
        else:
            try:
                if lun.startswith('lun'):
                    lun = lun[3:]
                lun = int(lun)
            except ValueError:
                self.shell.log.error("The LUN must be an integer value.")
                return
            else:
                if lun < 0:
                    self.shell.log.error("The LUN cannot be negative.")
                    return

        add_mapped_luns = \
                self.ui_eval_param(add_mapped_luns, 'bool',
                                   self.shell.prefs['auto_add_mapped_luns'])

        try:
            storage_object = self.get_node(storage_object).rtsnode
        except ValueError:
            self.shell.log.error("Invalid storage object %s." % storage_object)
            return

        lun_object = LUN(self.tpg, lun, storage_object)
        self.shell.log.info("Successfully created LUN %s." % lun_object.lun)
        ui_lun = UILUN(lun_object, self)

        if add_mapped_luns:
            for acl in self.tpg.node_acls:
                mapped_lun = lun
                existing_mluns = [mlun.mapped_lun for mlun in acl.mapped_luns]
                if mapped_lun in existing_mluns:
                    tentative_mlun = 0
                    while mapped_lun == lun:
                        if tentative_mlun not in existing_mluns:
                            mapped_lun = tentative_mlun
                            self.shell.log.warning(
                                "Mapped LUN %d already " % lun
                                + "exists in ACL %s, using %d instead."
                                % (acl.node_wwn, mapped_lun))
                        else:
                            tentative_mlun += 1
                mlun = MappedLUN(acl, mapped_lun, lun, write_protect=False)
                self.shell.log.info("Created mapped LUN %d in node ACL %s"
                                    % (mapped_lun, acl.node_wwn))
            self.parent.refresh()

        return self.new_node(ui_lun)

    def ui_complete_create(self, parameters, text, current_param):
        '''
        Parameter auto-completion method for user command create.
        @param parameters: Parameters on the command line.
        @type parameters: dict
        @param text: Current text of parameter being typed by the user.
        @type text: str
        @param current_param: Name of parameter to complete.
        @type current_param: str
        @return: Possible completions
        @rtype: list of str
        '''
        if current_param == 'storage_object':
            storage_objects = []
            for backstore in self.get_node('/backstores').children:
                for storage_object in backstore.children:
                    storage_objects.append(storage_object.path)
            completions = [so for so in storage_objects if so.startswith(text)]
        else:
            completions = []

        if len(completions) == 1:
            return [completions[0] + ' ']
        else:
            return completions

    def ui_command_delete(self, lun):
        '''
        Deletes the supplied LUN from the Target Portal Group. The I{lun} must
        be a positive number matching an existing LUN.

        Alternatively, the syntax I{lunX} where I{X} is a positive number is
        also accepted.

        SEE ALSO
        ========
        B{create}
        '''
        self.assert_root()
        if lun.startswith('lun'):
            lun = lun[3:]
        lun_object = LUN(self.tpg, lun)
        lun_object.delete()
        self.shell.log.info("Successfully deleted LUN %s." % lun)
        # Refresh the TPG as we need to also refresh acls MappedLUNs
        self.parent.refresh()

    def ui_complete_delete(self, parameters, text, current_param):
        '''
        Parameter auto-completion method for user command delete.
        @param parameters: Parameters on the command line.
        @type parameters: dict
        @param text: Current text of parameter being typed by the user.
        @type text: str
        @param current_param: Name of parameter to complete.
        @type current_param: str
        @return: Possible completions
        @rtype: list of str
        '''
        if current_param == 'lun':
            luns = [str(lun.lun) for lun in self.tpg.luns]
            completions = [lun for lun in luns if lun.startswith(text)]
        else:
            completions = []

        if len(completions) == 1:
            return [completions[0] + ' ']
        else:
            return completions


class UILUN(UIRTSLibNode):
    '''
    A generic UI for LUN objects.
    '''
    def __init__(self, lun, parent):
        name = "lun%d" % lun.lun
        UIRTSLibNode.__init__(self, name, lun, parent)
        self.cfs_cwd = lun.path
        self.refresh()

    def summary(self):
        lun = self.rtsnode
        is_healthy = True
        try:
            storage_object = lun.storage_object
        except RTSLibBrokenLink:
            description = "BROKEN STORAGE LINK"
            is_healthy = False
        else:
            backstore = storage_object.backstore
            if backstore.plugin.startswith("rd"):
                path = "ramdisk"
            else:
                path = storage_object.udev_path
            if self.shell.prefs['legacy_hba_view']:
                description = "%s%s/%s (%s)" % (backstore.plugin,
                                                backstore.index,
                                                storage_object.name, path)
            else:
                description = "%s/%s (%s)" % (backstore.plugin,
                                              dedup_so_name(storage_object),
                                              path)

        return (description, is_healthy)


class UIPortals(UINode):
    '''
    A generic UI for TPG network portals.
    '''
    def __init__(self, tpg, parent):
        UINode.__init__(self, "portals", parent)
        self.tpg = tpg
        self.cfs_cwd = "%s/np" % tpg.path
        self.refresh()

    def refresh(self):
        self._children = set([])
        for portal in self.tpg.network_portals:
            UIPortal(portal, self)

    def summary(self):
        no_portals = len(self._children)
        if no_portals != 1:
            msg = "%d Portals" % no_portals
        else:
            msg = "%d Portal" % no_portals
        return (msg, None)

    def ui_command_create(self, ip_address=None, ip_port=None):
        '''
        Creates a Network Portal with specified I{ip_address} and I{ip_port}.
        If I{ip_port} is omitted, the default port for the target fabric will
        be used. If I{ip_address} is omitted, the first IP address found
        matching the local hostname will be used.

        SEE ALSO
        ========
        B{delete}
        '''
        self.assert_root()
        if ip_port is None:
            # FIXME: Add a specfile parameter to determine that
            ip_port = 3260
            self.shell.log.info("Using default IP port %d" % ip_port)
        if ip_address is None:
            if not ip_address:
                ip_address = utils.get_main_ip()
                if ip_address:
                    self.shell.log.info("Automatically selected IP address %s."
                                        % ip_address)
                else:
                    self.shell.log.error("Cannot find a usable IP address to "
                                         + "create the Network Portal.")
                    return
        elif ip_address not in utils.list_eth_ips():
            self.shell.log.error("IP address does not exist: %s" % ip_address)
            return

        try:
            ip_port = int(ip_port)
        except ValueError:
            self.shell.log.error("The ip_port must be an integer value.")
            return

        portal = NetworkPortal(self.tpg, ip_address,
                               ip_port, mode='create')
        self.shell.log.info("Successfully created network portal %s:%d."
                            % (ip_address, ip_port))
        ui_portal = UIPortal(portal, self)
        return self.new_node(ui_portal)

    def ui_complete_create(self, parameters, text, current_param):
        '''
        Parameter auto-completion method for user command create.
        @param parameters: Parameters on the command line.
        @type parameters: dict
        @param text: Current text of parameter being typed by the user.
        @type text: str
        @param current_param: Name of parameter to complete.
        @type current_param: str
        @return: Possible completions
        @rtype: list of str
        '''
        if current_param == 'ip_address':
            completions = [addr for addr in utils.list_eth_ips()
                           if addr.startswith(text)]
        else:
            completions = []

        if len(completions) == 1:
            return [completions[0] + ' ']
        else:
            return completions

    def ui_command_delete(self, ip_address, ip_port):
        '''
        Deletes the Network Portal with specified I{ip_address} and I{ip_port}.

        SEE ALSO
        ========
        B{create}
        '''
        self.assert_root()
        portal = NetworkPortal(self.tpg, ip_address, ip_port, mode='lookup')
        portal.delete()
        self.shell.log.info("Deleted network portal %s:%s"
                            % (ip_address, ip_port))
        self.refresh()

    def ui_complete_delete(self, parameters, text, current_param):
        '''
        Parameter auto-completion method for user command delete.
        @param parameters: Parameters on the command line.
        @type parameters: dict
        @param text: Current text of parameter being typed by the user.
        @type text: str
        @param current_param: Name of parameter to complete.
        @type current_param: str
        @return: Possible completions
        @rtype: list of str
        '''
        completions = []
        # TODO: Check if a dict comprehension is acceptable here with supported
        #  XXX: python versions.
        portals = {}
        all_ports = set([])
        for portal in self.tpg.network_portals:
            all_ports.add(str(portal.port))
            if not portal.ip_address in portals:
                portals[portal.ip_address] = []
            portals[portal.ip_address].append(str(portal.port))

        if current_param == 'ip_address':
            if 'ip_port' in parameters:
                port = parameters['ip_port']
                completions = [addr for addr in portals
                               if port in portals[addr]
                               if addr.startswith(text)]
            else:
                completions = [addr for addr in portals
                               if addr.startswith(text)]
        elif current_param == 'ip_port':
            if 'ip_address' in parameters:
                addr = parameters['ip_address']
                if addr in portals:
                    completions = [port for port in portals[addr]
                                   if port.startswith(text)]
            else:
                completions = [port for port in all_ports
                               if port.startswith(text)]

        if len(completions) == 1:
            return [completions[0] + ' ']
        else:
            return completions


class UIPortal(UIRTSLibNode):
    '''
    A generic UI for a network portal.
    '''
    def __init__(self, portal, parent):
        name = "%s:%s" % (portal.ip_address, portal.port)
        UIRTSLibNode.__init__(self, name, portal, parent)
        self.cfs_cwd = portal.path
        self.portal = portal
        self.refresh()

    def summary(self):
        if self.portal._get_iser_attr():
            return ('OK, iser enabled', True)
        else:
            return ('OK, iser disabled', True)

    def ui_command_iser_enable(self):
        '''
        Enables iser operation on an network portal.
        '''
        if self.portal._get_iser_attr() == True:
            self.shell.log.info("iser operation has already been enabled")
        else:
            self.portal._set_iser_attr(True)
            self.shell.log.info("iser operation has been enabled")

    def ui_command_iser_disable(self):
        '''
        Disabled iser operation on an network portal.
        '''
        if self.portal._get_iser_attr() == False:
            self.shell.log.info("iser operation has already been disabled")
        else:
            self.portal._set_iser_attr(False)
            self.shell.log.info("iser operation has been disabled")
