# Copyright (C) 2012 Red Hat, Inc.  All rights reserved.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
#
# Authors: Jan Safranek <jsafrane@redhat.com>
# -*- coding: utf-8 -*-
"""
Module for ExtentProvider class.

ExtentProvider
--------------

.. autoclass:: ExtentProvider
    :members:

"""

from lmi.storage.DeviceProvider import DeviceProvider
import pywbem
import blivet.formats
import lmi.storage.util.storage as storage
import lmi.providers.cmpi_logging as cmpi_logging
from lmi.providers.ComputerSystem import get_system_name

class ExtentProvider(DeviceProvider):
    """
        Base of all StorageExtent providers.
        It fills common properties in the StorageExtent.
    """
    @cmpi_logging.trace_method
    def __init__(self, classname, *args, **kwargs):
        self.classname = classname
        super(ExtentProvider, self).__init__(*args, **kwargs)

    @cmpi_logging.trace_method
    def _get_device(self, object_name):
        """
            Get Anaconda StorageDevice for given name, without any checks.
        """
        path = object_name['DeviceID']
        device = storage.get_device_for_persistent_name(self.storage, path)
        return device

    @cmpi_logging.trace_method
    def provides_name(self, object_name):
        """
            Returns True, if this class is provider for given CIM InstanceName.
        """
        if not (object_name.has_key('SystemName')
                and object_name.has_key('SystemCreationClassName')
                and object_name.has_key('CreationClassName')
                and object_name.has_key('DeviceID')):
            return False
        if object_name['SystemName'] != get_system_name():
            return False

        if object_name['SystemCreationClassName'] != \
                self.config.system_class_name:
            return False

        if object_name['CreationClassName'] != self.classname:
            return False

        return True

    @cmpi_logging.trace_method
    def get_device_for_name(self, object_name):
        """
            Returns Anaconda StorageDevice for given CIM InstanceName or
            None if no device is found.
        """
        if self.provides_name(object_name):
            return self._get_device(object_name)

    @cmpi_logging.trace_method
    def get_name_for_device(self, device):
        """
            Returns CIM InstanceName for given Anaconda StorageDevice.
            None if no device is found.
        """
        name = pywbem.CIMInstanceName(self.classname,
                namespace=self.config.namespace,
                keybindings={
                    'SystemName' : get_system_name(),
                    'SystemCreationClassName' : self.config.system_class_name,
                    'CreationClassName' : self.classname,
                    'DeviceID': storage.get_persistent_name(device)
                })
        return name

    @cmpi_logging.trace_method
    def get_element_name(self, device):
        """
            Return ElementName property value for given StorageDevice.
            Device path (/dev/sda) is the default.
        """
        return device.name

    @cmpi_logging.trace_method
    # pylint: disable-msg=W0613
    def get_extent_status(self, device):
        """
            Return ExtentStatus property value for given StorageDevice.
            It must be array of int16 values.
        """
        return []

    @cmpi_logging.trace_method
    def get_size(self, device):
        """
            Return (BlockSize, NumberOfBlocks, ConsumableBlocks) properties
            for given StorageDevice.

            The ConsumableBlocks should be reduced by partition table size.
        """
        if device.partedDevice:
            block_size = pywbem.Uint64(device.partedDevice.sectorSize)
            total_blocks = device.partedDevice.length
            consumable_blocks = device.partedDevice.length
            if (device.format and isinstance(device.format,
                    blivet.formats.disklabel.DiskLabel)):
                # reduce by partition table size
                consumable_blocks -= storage.get_partition_table_size(
                        device)
        else:
            block_size = None
            total_blocks = None
            consumable_blocks = None
        return (block_size, total_blocks, consumable_blocks)

    @cmpi_logging.trace_method
    # pylint: disable-msg=W0613
    def get_primordial(self, device):
        """
            Returns True, if given StorageDevice is primordial.
        """
        if isinstance(device, blivet.devices.DiskDevice):
            return True
        else:
            return False

    @cmpi_logging.trace_method
    def get_discriminator(self, device):
        """
            Returns ExtentDiscriminator property value for given StorageDevice.
            It must return array of strings.
        """
        discriminator = []
        if device.format and isinstance(device.format,
                    blivet.formats.lvmpv.LVMPhysicalVolume):
            discriminator.append(self.Values.Discriminator.Pool_Component)
        return discriminator

    @cmpi_logging.trace_method
    def get_names(self, device):
        """
        Return array of all aliases (symlinks) of the device. The real
        device path is always the first element of the array.

        :param device: (``StorageDevice``) The device.
        :returns: ``array of strings``.
        """
        return [device.path] + device.deviceLinks

    @cmpi_logging.trace_method
    def get_instance_id(self, device):
        """
        Return InstanceID for given device.

        :param device: (``StorageDevice``) The device.
        :returns: ``string``.
        """
        return ("LMI:" + self.classname + ":"
                + storage.get_persistent_name(device))

    @cmpi_logging.trace_method
    # pylint: disable-msg=W0221
    def get_instance(self, env, model, device=None):
        """
            Provider implementation of GetInstance intrinsic method.
            It fills common StorageExtent properties.
        """
        if not self.provides_name(model):
            raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND, "Wrong keys.")
        if not device:
            device = self._get_device(model)
        if not device:
            raise pywbem.CIMError(pywbem.CIM_ERR_NOT_FOUND,
                    "Cannot find the extent.")

        model['ElementName'] = self.get_element_name(device)
        model['NameNamespace'] = self.Values.NameNamespace.OS_Device_Namespace
        model['NameFormat'] = self.Values.NameFormat.OS_Device_Name
        model['Name'] = device.path
        model['InstanceID'] = self.get_instance_id(device)

        extent_status = self.get_extent_status(device)
        model['ExtentStatus'] = pywbem.CIMProperty(
                name='ExtentStatus',
                value=extent_status,
                type='uint16',
                array_size=len(extent_status),
                is_array=True)

        operational_status = self.get_status(device)
        model['OperationalStatus'] = pywbem.CIMProperty(
                name='OperationalStatus',
                value=operational_status,
                type='uint16',
                array_size=len(operational_status),
                is_array=True)

        (block_size, total_blocks, consumable_blocks) = self.get_size(device)
        if block_size:
            model['BlockSize'] = pywbem.Uint64(block_size)
        if total_blocks:
            model['NumberOfBlocks'] = pywbem.Uint64(total_blocks)
        if consumable_blocks:
            model['ConsumableBlocks'] = pywbem.Uint64(consumable_blocks)

        redundancy = self.get_redundancy(device)
        model['NoSinglePointOfFailure'] = redundancy.no_single_point_of_failure
        model['DataRedundancy'] = pywbem.Uint16(redundancy.data_redundancy)
        model['PackageRedundancy'] = pywbem.Uint16(
                redundancy.package_redundancy)
        model['ExtentStripeLength'] = pywbem.Uint64(redundancy.stripe_length)
        model['IsComposite'] = (len(device.parents) > 1)

        # TODO: add DeltaReservation (mandatory in SMI-S)

        model['Primordial'] = self.get_primordial(device)
        model['Names'] = self.get_names(device)

        discriminator = self.get_discriminator(device)
        model['ExtentDiscriminator'] = pywbem.CIMProperty(
                name='ExtentDiscriminator',
                value=discriminator,
                type='string',
                array_size=len(discriminator),
                is_array=True)

        return model

    @cmpi_logging.trace_method
    def enumerate_devices(self):
        """
            Enumerate all StorageDevices, that this provider provides.
        """
        pass


    @cmpi_logging.trace_method
    def enum_instances(self, env, model, keys_only):
        """Enumerate instances.

        The WBEM operations EnumerateInstances and EnumerateInstanceNames
        are both mapped to this method.
        This method is a python generator

        Keyword arguments:
        env -- Provider Environment (pycimmb.ProviderEnvironment)
        model -- A template of the pywbem.CIMInstances to be generated.
            The properties of the model are already filtered according to
            the PropertyList from the request.  Only properties present in
            the model need to be given values.  If you prefer, you can
            always set all of the values, and the instance will be filtered
            for you.
        keys_only -- A boolean.  True if only the key properties should be
            set on the generated instances.

        Possible Errors:
        CIM_ERR_FAILED (some other unspecified error occurred)

        """
        model.path.update({'CreationClassName': None, 'SystemName': None,
            'DeviceID': None, 'SystemCreationClassName': None})

        for device in self.enumerate_devices():
            name = self.get_name_for_device(device)
            model['SystemName'] = name['SystemName']
            model['SystemCreationClassName'] = name['SystemCreationClassName']
            model['CreationClassName'] = name['CreationClassName']
            model['DeviceID'] = name['DeviceID']
            if keys_only:
                yield model
            else:
                yield self.get_instance(env, model, device)

    class Values(DeviceProvider.Values):
        class NameNamespace(object):
            Unknown = pywbem.Uint16(0)
            Other = pywbem.Uint16(1)
            VPD83Type3 = pywbem.Uint16(2)
            VPD83Type2 = pywbem.Uint16(3)
            VPD83Type1 = pywbem.Uint16(4)
            VPD80 = pywbem.Uint16(5)
            NodeWWN = pywbem.Uint16(6)
            SNVM = pywbem.Uint16(7)
            OS_Device_Namespace = pywbem.Uint16(8)

        class NameFormat(object):
            Unknown = pywbem.Uint16(0)
            Other = pywbem.Uint16(1)
            VPD83NAA6 = pywbem.Uint16(2)
            VPD83NAA5 = pywbem.Uint16(3)
            VPD83Type2 = pywbem.Uint16(4)
            VPD83Type1 = pywbem.Uint16(5)
            VPD83Type0 = pywbem.Uint16(6)
            SNVM = pywbem.Uint16(7)
            NodeWWN = pywbem.Uint16(8)
            NAA = pywbem.Uint16(9)
            EUI64 = pywbem.Uint16(10)
            T10VID = pywbem.Uint16(11)
            OS_Device_Name = pywbem.Uint16(12)

        class ExtentStatus(object):
            Other = pywbem.Uint16(0)
            Unknown = pywbem.Uint16(1)
            None_Not_Applicable = pywbem.Uint16(2)
            Broken = pywbem.Uint16(3)
            Data_Lost = pywbem.Uint16(4)
            Dynamic_Reconfig = pywbem.Uint16(5)
            Exposed = pywbem.Uint16(6)
            Fractionally_Exposed = pywbem.Uint16(7)
            Partially_Exposed = pywbem.Uint16(8)
            Protection_Disabled = pywbem.Uint16(9)
            Readying = pywbem.Uint16(10)
            Rebuild = pywbem.Uint16(11)
            Recalculate = pywbem.Uint16(12)
            Spare_in_Use = pywbem.Uint16(13)
            Verify_In_Progress = pywbem.Uint16(14)
            In_Band_Access_Granted = pywbem.Uint16(15)
            Imported = pywbem.Uint16(16)
            Exported = pywbem.Uint16(17)
            Relocating = pywbem.Uint16(18)
            # DMTF_Reserved = ..

        class Usage(object):
            Other = pywbem.Uint16(1)
            Unrestricted = pywbem.Uint16(2)
            Reserved_for_ComputerSystem__the_block_server_ = pywbem.Uint16(3)
            Reserved_by_Replication_Services = pywbem.Uint16(4)
            Reserved_by_Migration_Services = pywbem.Uint16(5)
            Local_Replica_Source = pywbem.Uint16(6)
            Remote_Replica_Source = pywbem.Uint16(7)
            Local_Replica_Target = pywbem.Uint16(8)
            Remote_Replica_Target = pywbem.Uint16(9)
            Local_Replica_Source_or_Target = pywbem.Uint16(10)
            Remote_Replica_Source_or_Target = pywbem.Uint16(11)
            Delta_Replica_Target = pywbem.Uint16(12)
            Element_Component = pywbem.Uint16(13)
            Reserved_as_Pool_Contributor = pywbem.Uint16(14)
            Composite_Volume_Member = pywbem.Uint16(15)
            Composite_LogicalDisk_Member = pywbem.Uint16(16)
            Reserved_for_Sparing = pywbem.Uint16(17)
            # DMTF_Reserved = ..
            # Vendor_Reserved = 32768..65535

        class Discriminator(object):
            Pool_Component = 'SNIA:PoolComponent'
            Composite = 'SNIA:Composite'
            Imported = 'SNIA:Imported'
            Allocated = 'SNIA:Allocated'
