# Copyright (c) 2016 Huawei Technologies Co., Ltd.
# All Rights Reserved.
#
#    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 oslo_log import log as logging
from oslo_utils import excutils

from cinder import context
from cinder import exception
from cinder.i18n import _, _LI
from cinder.volume.drivers.huawei import constants
from cinder.volume import qos_specs

LOG = logging.getLogger(__name__)


class SmartQos(object):
    def __init__(self, client):
        self.client = client

    @staticmethod
    def get_qos_by_volume_type(volume_type):
        # We prefer the qos_specs association
        # and override any existing extra-specs settings
        # if present.
        if not volume_type:
            return {}

        qos_specs_id = volume_type.get('qos_specs_id')
        if not qos_specs_id:
            return {}

        qos = {}
        io_type_flag = None
        ctxt = context.get_admin_context()
        kvs = qos_specs.get_qos_specs(ctxt, qos_specs_id)['specs']
        LOG.info(_LI('The QoS sepcs is: %s.'), kvs)
        for k, v in kvs.items():
            if k not in constants.HUAWEI_VALID_KEYS:
                continue
            if k != 'IOType' and int(v) <= 0:
                msg = _('QoS config is wrong. %s must > 0.') % k
                LOG.error(msg)
                raise exception.InvalidInput(reason=msg)
            if k == 'IOType':
                if v not in ['0', '1', '2']:
                    msg = _('Illegal value specified for IOTYPE: 0, 1, or 2.')
                    LOG.error(msg)
                    raise exception.InvalidInput(reason=msg)
                io_type_flag = 1
                qos[k.upper()] = v
            else:
                qos[k.upper()] = v

        if not io_type_flag:
            msg = (_('QoS policy must specify for IOTYPE: 0, 1, or 2, '
                     'QoS policy: %(qos_policy)s ') % {'qos_policy': qos})
            LOG.error(msg)
            raise exception.InvalidInput(reason=msg)

        # QoS policy must specify for IOTYPE and another qos_specs.
        if len(qos) < 2:
            msg = (_('QoS policy must specify for IOTYPE and another '
                     'qos_specs, QoS policy: %(qos_policy)s.')
                   % {'qos_policy': qos})
            LOG.error(msg)
            raise exception.InvalidInput(reason=msg)

        for upper_limit in constants.UPPER_LIMIT_KEYS:
            for lower_limit in constants.LOWER_LIMIT_KEYS:
                if upper_limit in qos and lower_limit in qos:
                    msg = (_('QoS policy upper_limit and lower_limit '
                             'conflict, QoS policy: %(qos_policy)s.')
                           % {'qos_policy': qos})
                    LOG.error(msg)
                    raise exception.InvalidInput(reason=msg)

        return qos

    def _is_high_priority(self, qos):
        """Check QoS priority."""
        for key, value in qos.items():
            if (key.find('MIN') == 0) or (key.find('LATENCY') == 0):
                return True

        return False

    def add(self, qos, lun_id):
        policy_id = None
        try:
            # Check QoS priority.
            if self._is_high_priority(qos):
                self.client.change_lun_priority(lun_id)
            # Create QoS policy and activate it.
            version = self.client.find_array_version()
            if version >= constants.ARRAY_VERSION:
                (qos_id, lun_list) = self.client.find_available_qos(qos)
                if qos_id:
                    self.client.add_lun_to_qos(qos_id, lun_id, lun_list)
                else:
                    policy_id = self.client.create_qos_policy(qos, lun_id)
                    self.client.activate_deactivate_qos(policy_id, True)
            else:
                policy_id = self.client.create_qos_policy(qos, lun_id)
                self.client.activate_deactivate_qos(policy_id, True)
        except exception.VolumeBackendAPIException:
            with excutils.save_and_reraise_exception():
                if policy_id is not None:
                    self.client.delete_qos_policy(policy_id)

    def remove(self, qos_id, lun_id):
        qos_info = self.client.get_qos_info(qos_id)
        lun_list = self.client.get_lun_list_in_qos(qos_id, qos_info)
        if len(lun_list) <= 1:
            qos_status = qos_info['RUNNINGSTATUS']
            # 2: Active status.
            if qos_status == constants.STATUS_QOS_ACTIVE:
                self.client.activate_deactivate_qos(qos_id, False)
            self.client.delete_qos_policy(qos_id)
        else:
            self.client.remove_lun_from_qos(lun_id, lun_list, qos_id)


class SmartPartition(object):
    def __init__(self, client):
        self.client = client

    def add(self, opts, lun_id):
        if opts['smartpartition'] != 'true':
            return
        if not opts['partitionname']:
            raise exception.InvalidInput(
                reason=_('Partition name is None, please set '
                         'smartpartition:partitionname in key.'))

        partition_id = self.client.get_partition_id_by_name(
            opts['partitionname'])
        if not partition_id:
            raise exception.InvalidInput(
                reason=(_('Can not find partition id by name %(name)s.')
                        % {'name': opts['partitionname']}))

        self.client.add_lun_to_partition(lun_id, partition_id)


class SmartCache(object):
    def __init__(self, client):
        self.client = client

    def add(self, opts, lun_id):
        if opts['smartcache'] != 'true':
            return
        if not opts['cachename']:
            raise exception.InvalidInput(
                reason=_('Cache name is None, please set '
                         'smartcache:cachename in key.'))

        cache_id = self.client.get_cache_id_by_name(opts['cachename'])
        if not cache_id:
            raise exception.InvalidInput(
                reason=(_('Can not find cache id by cache name %(name)s.')
                        % {'name': opts['cachename']}))

        self.client.add_lun_to_cache(lun_id, cache_id)


class SmartX(object):
    def get_smartx_specs_opts(self, opts):
        # Check that smarttier is 0/1/2/3
        opts = self.get_smarttier_opts(opts)
        opts = self.get_smartthin_opts(opts)
        opts = self.get_smartcache_opts(opts)
        opts = self.get_smartpartition_opts(opts)
        return opts

    def get_smarttier_opts(self, opts):
        if opts['smarttier'] == 'true':
            if not opts['policy']:
                opts['policy'] = '1'
            elif opts['policy'] not in ['0', '1', '2', '3']:
                raise exception.InvalidInput(
                    reason=(_('Illegal value specified for smarttier: '
                              'set to either 0, 1, 2, or 3.')))
        else:
            opts['policy'] = '0'

        return opts

    def get_smartthin_opts(self, opts):
        if opts['thin_provisioning_support'] == 'true':
            if opts['thick_provisioning_support'] == 'true':
                raise exception.InvalidInput(
                    reason=(_('Illegal value specified for thin: '
                              'Can not set thin and thick at the same time.')))
            else:
                opts['LUNType'] = constants.THIN_LUNTYPE
        if opts['thick_provisioning_support'] == 'true':
            opts['LUNType'] = constants.THICK_LUNTYPE

        return opts

    def get_smartcache_opts(self, opts):
        if opts['smartcache'] == 'true':
            if not opts['cachename']:
                raise exception.InvalidInput(
                    reason=_('Cache name is None, please set '
                             'smartcache:cachename in key.'))
        else:
            opts['cachename'] = None

        return opts

    def get_smartpartition_opts(self, opts):
        if opts['smartpartition'] == 'true':
            if not opts['partitionname']:
                raise exception.InvalidInput(
                    reason=_('Partition name is None, please set '
                             'smartpartition:partitionname in key.'))
        else:
            opts['partitionname'] = None

        return opts
