# Copyright (C) 2014 Hewlett-Packard Development Company, L.P.
#
# 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 cinder.scheduler.evaluator import evaluator
from cinder.scheduler import weights


LOG = logging.getLogger(__name__)


class GoodnessWeigher(weights.BaseHostWeigher):
    """Goodness Weigher.  Assign weights based on a host's goodness function.

    Goodness rating is the following:

    .. code-block:: none

          0 -- host is a poor choice
          .
          .
         50 -- host is a good choice
          .
          .
        100 -- host is a perfect choice

    """

    def _weigh_object(self, host_state, weight_properties):
        """Determine host's goodness rating based on a goodness_function."""
        stats = self._generate_stats(host_state, weight_properties)
        LOG.debug("Checking host '%s'", stats[0]['host_stats']['host'])
        # Run the goodness function for all possible storage_protocol values
        # (e.g. FC, fibre_channel) and use the maximum value, as the function
        # may look for an exact match on a protocol and the backend may be
        # returning a variant.
        result = max(self._check_goodness_function(stat) for stat in stats)
        LOG.debug("Goodness weight for %(host)s: %(res)s",
                  {'res': result, 'host': stats[0]['host_stats']['host']})

        return result

    def _check_goodness_function(self, stats):
        """Gets a host's goodness rating based on its goodness function."""

        goodness_rating = 0

        if stats['goodness_function'] is None:
            LOG.warning("Goodness function not set :: defaulting to "
                        "minimal goodness rating of 0")
        else:
            try:
                goodness_result = self._run_evaluator(
                    stats['goodness_function'],
                    stats)
            except Exception as ex:
                LOG.warning("Error in goodness_function function "
                            "'%(function)s' : '%(error)s' :: Defaulting "
                            "to a goodness of 0",
                            {'function': stats['goodness_function'],
                             'error': ex, })
                return goodness_rating

            if type(goodness_result) is bool:
                if goodness_result:
                    goodness_rating = 100
            elif goodness_result < 0 or goodness_result > 100:
                LOG.warning("Invalid goodness result.  Result must be "
                            "between 0 and 100.  Result generated: '%s' "
                            ":: Defaulting to a goodness of 0",
                            goodness_result)
            else:
                goodness_rating = goodness_result

        return goodness_rating

    def _run_evaluator(self, func, stats):
        """Evaluates a given function using the provided available stats."""
        host_stats = stats['host_stats']
        host_caps = stats['host_caps']
        extra_specs = stats['extra_specs']
        qos_specs = stats['qos_specs']
        volume_stats = stats['volume_stats']

        result = evaluator.evaluate(
            func,
            extra=extra_specs,
            stats=host_stats,
            capabilities=host_caps,
            volume=volume_stats,
            qos=qos_specs)

        return result

    def _generate_stats(self, host_state, weight_properties):
        """Generates statistics from host and volume data.

        Returns a list where each entry corresponds to a different
        storage_protocol value for those backends that use a storage protocol
        that has variants, but only if the function actually uses the protocol.
        """
        host_stats = {
            'host': host_state.host,
            'volume_backend_name': host_state.volume_backend_name,
            'vendor_name': host_state.vendor_name,
            'driver_version': host_state.driver_version,
            'storage_protocol': host_state.storage_protocol,
            'QoS_support': host_state.QoS_support,
            'total_capacity_gb': host_state.total_capacity_gb,
            'allocated_capacity_gb': host_state.allocated_capacity_gb,
            'free_capacity_gb': host_state.free_capacity_gb,
            'reserved_percentage': host_state.reserved_percentage,
            'updated': host_state.updated,
        }

        host_caps = host_state.capabilities

        goodness_function = None
        uses_protocol = False

        if ('goodness_function' in host_caps and
                host_caps['goodness_function'] is not None):
            goodness_function = str(host_caps['goodness_function'])
            uses_protocol = 'storage_protocol' in goodness_function

        qos_specs = weight_properties.get('qos_specs', {}) or {}

        volume_type = weight_properties.get('volume_type', {}) or {}
        extra_specs = volume_type.get('extra_specs', {})

        request_spec = weight_properties.get('request_spec', {}) or {}
        volume_stats = request_spec.get('volume_properties', {})

        stats = {
            'host_stats': host_stats,
            'host_caps': host_caps,
            'extra_specs': extra_specs,
            'qos_specs': qos_specs,
            'volume_stats': volume_stats,
            'volume_type': volume_type,
            'goodness_function': goodness_function,
        }

        # Only create individual entries for the different protocols variants
        # if the function uses the protocol and there are variants.
        if uses_protocol and isinstance(host_state.storage_protocol, list):
            result = []
            for protocol in host_state.storage_protocol:
                new_stats = stats.copy()
                new_stats['host_stats'] = dict(new_stats['host_stats'])
                new_stats['host_stats']['storage_protocol'] = protocol
                new_stats['host_caps'] = dict(new_stats['host_caps'])
                new_stats['host_caps']['storage_protocol'] = protocol
                result.append(new_stats)

        else:
            result = [stats]

        return result
