# Copyright (c) 2015 Mellanox Technologies, 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.

import abc
import collections

from neutron_lib.agent import l2_extension
from neutron_lib import constants
from neutron_lib.services.qos import constants as qos_consts
from oslo_concurrency import lockutils
from oslo_log import log as logging
import six

from neutron.api.rpc.callbacks.consumer import registry
from neutron.api.rpc.callbacks import events
from neutron.api.rpc.callbacks import resources
from neutron.api.rpc.handlers import resources_rpc
from neutron import manager

LOG = logging.getLogger(__name__)


@six.add_metaclass(abc.ABCMeta)
class QosAgentDriver(object):
    """Defines stable abstract interface for QoS Agent Driver.

    QoS Agent driver defines the interface to be implemented by Agent
    for applying QoS Rules on a port.
    """

    # Each QoS driver should define the set of rule types that it supports, and
    # corresponding handlers that has the following names:
    #
    # create_<type>
    # update_<type>
    # delete_<type>
    #
    # where <type> is one of VALID_RULE_TYPES
    # There is exception from this rule for deletion of rules with
    # attribute direction set to ingress (e.g. bandwidth limit rule).
    # For deletion of such rule types delete handler has following name:
    # delete_<type>_ingress

    @abc.abstractmethod
    def initialize(self):
        """Perform QoS agent driver initialization.
        """

    def create(self, port, qos_policy):
        """Apply QoS rules on port for the first time.

        :param port: port object.
        :param qos_policy: the QoS policy to be applied on port.
        """
        self._handle_update_create_rules('create', port, qos_policy)

    def consume_api(self, agent_api):
        """Consume the AgentAPI instance from the QoSAgentExtension class

        This allows QosAgentDrivers to gain access to resources limited to the
        NeutronAgent when this method is overridden.

        :param agent_api: An instance of an agent specific API
        """

    def update(self, port, qos_policy):
        """Apply QoS rules on port.

        :param port: port object.
        :param qos_policy: the QoS policy to be applied on port.
        """
        self._handle_update_create_rules('update', port, qos_policy)

    def delete(self, port, qos_policy=None):
        """Remove QoS rules from port.

        :param port: port object.
        :param qos_policy: the QoS policy to be removed from port.
        """
        if qos_policy is None:
            for rule_type in self.SUPPORTED_RULES:
                self._handle_rule_delete(port, rule_type)
                if self._rule_type_has_ingress_direction(rule_type):
                    self._handle_rule_delete(port, rule_type, ingress=True)
        else:
            for rule in self._iterate_rules(qos_policy.rules):
                self._handle_rule_delete(
                    port, rule.rule_type,
                    ingress=self._rule_is_ingress_direction(rule))

    def _iterate_rules(self, rules):
        for rule in rules:
            rule_type = rule.rule_type
            if rule_type in self.SUPPORTED_RULES:
                yield rule
            else:
                LOG.warning('Unsupported QoS rule type for %(rule_id)s: '
                            '%(rule_type)s; skipping',
                            {'rule_id': rule.id, 'rule_type': rule_type})

    def _handle_rule_delete(self, port, rule_type, ingress=False):
        handler_name = "".join(("delete_", rule_type))
        if ingress:
            handler_name = "%s_%s" % (handler_name,
                                      constants.INGRESS_DIRECTION)
        handler = getattr(self, handler_name)
        handler(port)

    def _handle_update_create_rules(self, action, port, qos_policy):
        for rule in self._iterate_rules(qos_policy.rules):
            if rule.should_apply_to_port(port):
                handler_name = "".join((action, "_", rule.rule_type))
                handler = getattr(self, handler_name)
                handler(port, rule)
            else:
                LOG.debug("Port %(port)s excluded from QoS rule %(rule)s",
                          {'port': port, 'rule': rule.id})

    def _rule_type_has_ingress_direction(self, rule_type):
        supported_rule = self.SUPPORTED_RULES[rule_type]
        if qos_consts.DIRECTION not in supported_rule.keys():
            return False
        return (constants.INGRESS_DIRECTION in
                supported_rule[qos_consts.DIRECTION]['type:values'])

    def _rule_is_ingress_direction(self, rule):
        rule_direction = getattr(rule, "direction", constants.EGRESS_DIRECTION)
        return rule_direction == constants.INGRESS_DIRECTION