"""
MultipathDevices - command ``multipath -v4 -ll``
================================================

This function converts the output of the ``multipath -v4 -ll`` command and
stores the data around each multipath device given.

Examples:
    >>> type(mpaths)
    <class 'insights.parsers.multipath_v4_ll.MultipathDevices'>
    >>> len(mpaths)  # Can treat the object as a list to iterate through
    3
    >>> mpaths[0]['alias']
    'mpathg'
    >>> mpaths[0]['size']
    '54T'
    >>> mpaths[0]['dm_name']
    'dm-2'
    >>> mpaths[0]['wwid']
    '36f01faf000da360b0000033c528fea6d'
    >>> groups = mpaths[0]['path_group']  # List of path groups for this device
    >>> groups[0]['status']
    'active'
    >>> len(groups[0]['path'])
    4
    >>> path0 = groups[0]['path'][0]  # Each path group has an array of paths
    >>> path0[1]  # Paths are stored as a list of items
    'sdc'
    >>> path0[-1]
    'running'
    >>> mpaths.dms  # List of device names found
    ['dm-2', 'dm-4', 'dm-5']
    >>> mpaths.by_dm['dm-2']['alias']  # Access by device name
    'mpathg'
    >>> mpaths.aliases  # Aliases found (again, in order)
    ['mpathg', 'mpathe']
    >>> mpaths.by_alias['mpathg']['dm_name']  # Access by alias
    'dm-2'
    >>> mpaths.by_wwid['36f01faf000da360b0000033c528fea6d']['dm_name']
    'dm-2'
"""

import re
import shlex
from insights import parser, CommandParser
from insights.specs import Specs


@parser(Specs.multipath__v4__ll)
class MultipathDevices(CommandParser):
    """
    ``multipath_-v4_ll`` command output

    Example input::

        ===== paths list =====
        uuid hcil    dev dev_t pri dm_st chk_st vend/prod/rev       dev_st
             0:0:0:0 sda 8:0   -1  undef ready  VMware,Virtual disk running
             3:0:0:1 sdb 8:16  -1  undef ready  IET,VIRTUAL-DISK    running
             4:0:0:1 sdc 8:32  -1  undef ready  IET,VIRTUAL-DISK    running
        Oct 28 14:02:44 | *word = 0, len = 1
        Oct 28 14:02:44 | *word = E, len = 1
        Oct 28 14:02:44 | *word = 1, len = 1
        Oct 28 14:02:44 | *word = 0, len = 1
        Oct 28 14:02:44 | *word = A, len = 1
        Oct 28 14:02:44 | *word = 0, len = 1
        mpathg (36f01faf000da360b0000033c528fea6d) dm-2 DELL,MD36xxi
        size=54T features='3 queue_if_no_path pg_init_retries 50' hwhandler='1 rdac' wp=rw
        |-+- policy='round-robin 0' prio=0 status=active
        | |- 12:0:0:1 sdc 8:32   active ready running
        | |- 11:0:0:1 sdi 8:128  active ready running
        | |- 15:0:0:1 sdo 8:224  active ready running
        | `- 17:0:0:1 sdv 65:80  active ready running
        `-+- policy='round-robin 0' prio=0 status=enabled
          |- 13:0:0:1 sdf 8:80   active ready running
          |- 14:0:0:1 sdl 8:176  active ready running
          |- 16:0:0:1 sdr 65:16  active ready running
          `- 18:0:0:1 sdx 65:112 active ready running
        mpathe (36f01faf000da3761000004323aa6fbce) dm-4 DELL,MD36xxi
        size=54T features='3 queue_if_no_path pg_init_retries 55' hwhandler='1 rdac' wp=rw
        |-+- policy='round-robin 0' prio=0 status=active
        | |- 13:0:0:2 sdg 8:96   active faulty running
        | |- 14:0:0:2 sdm 8:192  active faulty running
        | |- 16:0:0:2 sds 65:32  active faulty running
        | `- 18:0:0:2 sdy 65:128 active faulty running
        `-+- policy='round-robin 0' prio=0 status=enabled
          |- 12:0:0:2 sdd 8:48   active faulty running
          |- 11:0:0:2 sdj 8:144  active faulty running
          |- 15:0:0:2 sdp 8:240  active faulty running
          `- 17:0:0:2 sdw 65:96  active faulty running
        36001405b1629f80d52a4c898f8856e43 dm-5 LIO-ORG ,block0_sdb
        size=2.0G features='0' hwhandler='0' wp=rw
        |-+- policy='service-time 0' prio=1 status=active
        | `- 3:0:0:0 sdc 8:32 active ready running
        `-+- policy='service-time 0' prio=1 status=enabled
          `- 4:0:0:0 sdb 8:16 active ready running

    Example data structure produced::

        devices = [
          {
            "alias": "mpathg",
            "wwid": "36f01faf000da360b0000033c528fea6d",
            "dm_name": "dm-2",
            "venprod": "DELL,MD36xxi",
            "size": "54T",
            "features": "3 queue_if_no_path pg_init_retries 50",
            "hwhandler": "1 rdac",
            "wp": "rw",
            "path_group": [
                 {
                    "policy": "round-robin 0",
                    "prio": "0"
                    "status": "active"
                    "path": [
                        ['12:0:0:1', 'sdc', '8:32', 'active', 'ready', 'running'],
                        ['11:0:0:1', 'sdi', '8:128', 'active', 'ready', 'running'],
                        ['15:0:0:1', 'sdo', '8:224', 'active', 'ready', 'running'],
                        ['17:0:0:1', 'sdv', '65:80', 'active', 'ready', 'running']
                    ]
                 }, {
                    "policy": "round-robin 0",
                    "prio": "0"
                    "status": "enabled"
                    "path": [
                        ['13:0:0:1', 'sdf', '8:80', 'active', 'ready', 'running'],
                        ['14:0:0:1', 'sdl', '8:176', 'active', 'ready', 'running'],
                        ['16:0:0:1', 'sdr', '65:16', 'active', 'ready', 'running'],
                        ['18:0:0:1', 'sdx', '65:112','active', 'ready', 'running']
                    ]
                }
            ]
          },...
        ]

        raw_info_lines = [
            "===== paths list =====",
            "uuid hcil    dev dev_t pri dm_st chk_st vend/prod/rev       dev_st",
            "     0:0:0:0 sda 8:0   -1  undef ready  VMware,Virtual disk running",
            "     3:0:0:1 sdb 8:16  -1  undef ready  IET,VIRTUAL-DISK    running",
            "     4:0:0:1 sdc 8:32  -1  undef ready  IET,VIRTUAL-DISK    running",
            "Oct 28 14:02:44 | *word = 0, len = 1",
            ...
        ]

    Attributes:
        devices (list): List of devices found, in order
        dms (list): Device mapper names of each device, in order found
        aliases (list): Alias of each device, in order found
        wwids (list):  World Wide ID
        by_dm (dict): Access to each device by device mapper name
        by_alias (dict): Access to each device by alias
        by_wwid (dict): Access to each device by World Wide ID
        raw_info_lines(list): List of raw info lines found, in order
    """

    def parse_content(self, content):
        self.devices = []
        self.raw_info_lines = []

        mpath_dev_all = []
        mpath_dev = {}
        path_info = []
        path_group = []
        path_group_attr = {}

        MPATH_WWID_REG = re.compile(r'\(?([A-Za-z0-9_\s]+)\)?\s+dm-')
        PROPERTY_SQBRKT_REG = re.compile(r"\[(?P<key>\w+)=(?P<value>[^\]]+)\]")
        PATHGROUP_POLICY_STR = \
            r"(?:policy=')?(?P<policy>(?:round-robin|queue-length|service-time) \d)" + \
            r"(?:' | \[)prio=(?P<priority>\d+)(?:\]\[| status=)" + \
            r"(?P<status>\w+)(?:\]|)"
        PATHGROUP_POLICY_REG = re.compile(PATHGROUP_POLICY_STR)
        HCTL_REG = re.compile(r'(?:[`|]-(?:\+-)?|\\_) (\d+:){3}\d+')

        for line in content:
            m = MPATH_WWID_REG.search(line)

            if m:
                # Save previous path group info if we have any:
                # Now that we've got a valid path, append the group data if we
                # haven't already
                if path_info:
                    path_group_attr['path'] = path_info
                    path_group.append(path_group_attr)
                    # Must reset path group info to not carry on into new device
                    path_group_attr = {}
                    path_info = []
                    mpath_dev['path_group'] = path_group
                    mpath_dev_all.append(mpath_dev)

                mpath_dev = {}
                path_group = []
                wwid = m.group(1)
                no_alias = line.startswith(wwid)
                (dm, venprod) = re.findall(r".*(dm-\S+)\s+(.*)", line)[0]
                if not no_alias:
                    (dm, venprod) = re.findall(r"\w+\s+\(.*\)\s+(dm-\S+)\s+(.*)", line)[0]
                    mpath_dev['alias'] = line.split()[0]
                mpath_dev['wwid'] = wwid
                mpath_dev['dm_name'] = dm
                mpath_dev['venprod'] = venprod

            elif 'size=' in line:
                if '][' in line:
                    # Old RHEL 5 format:
                    for (k, v) in PROPERTY_SQBRKT_REG.findall(line):
                        mpath_dev[k] = v
                    # Handle un-named write policy attribute on the end:
                    mpath_dev['wp'] = line[-3:-1]
                else:
                    # Newer RHEL 6 format:
                    attr_line = shlex.split(line)
                    for item in attr_line:
                        (k, v) = item.split('=', 1)
                        mpath_dev[k] = v

            elif PATHGROUP_POLICY_REG.search(line):
                m = PATHGROUP_POLICY_REG.search(line)
                # New path info - save the previous if we have one:
                if path_info:
                    path_group_attr['path'] = path_info
                    path_group.append(path_group_attr)

                path_group_attr = {}
                path_info = []
                path_group_attr['policy'] = m.group('policy')
                path_group_attr['prio'] = m.group('priority')
                path_group_attr['status'] = m.group('status')

            elif HCTL_REG.search(line):
                colon_index = line.index(":")
                # Dodgy hack to convert RHEL 5 attributes in square brackets into
                # spaced out words to combine into the list
                line = line.replace('[', ' ').replace(']', ' ')
                path_info.append(line[colon_index - 2:].split())
            else:
                self.raw_info_lines.append(line)

        # final save of outstanding path and path group data:
        if path_info:
            path_group_attr['path'] = path_info
            path_group.append(path_group_attr)
        if path_group:
            mpath_dev['path_group'] = path_group
            mpath_dev_all.append(mpath_dev)

        self.devices = mpath_dev_all

        # Create some extra accessor properties
        self.dms = [path['dm_name'] for path in self.devices if 'dm_name' in path]
        self.by_dm = dict((path['dm_name'], path) for path in self.devices if 'dm_name' in path)
        self.aliases = [path['alias'] for path in self.devices if 'alias' in path]
        self.by_alias = dict((path['alias'], path) for path in self.devices if 'alias' in path)
        self.wwids = [path['wwid'] for path in self.devices if 'wwid' in path]
        self.by_wwid = dict((path['wwid'], path) for path in self.devices if 'wwid' in path)

    def __len__(self):
        """
        The length of the devices list
        """
        return len(self.devices)

    def __iter__(self):
        """
        Iterate through the devices list
        """
        for device in self.devices:
            yield device

    def __getitem__(self, idx):
        """
        Fetch a device by index in devices list
        """
        return self.devices[idx]
