# Copyright 2013 VMware, Inc.
# 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

from neutron.api import extensions
from neutron.api.v2 import base

from neutron_lib.api import converters
from neutron_lib.api import extensions as api_extensions
from neutron_lib.db import constants as db_const
from neutron_lib import exceptions as nexception
from neutron_lib.plugins import directory

from vmware_nsx._i18n import _

# For policy.json/Auth
qos_queue_create = "create_qos_queue"
qos_queue_delete = "delete_qos_queue"
qos_queue_get = "get_qos_queue"
qos_queue_list = "get_qos_queues"


class DefaultQueueCreateNotAdmin(nexception.InUse):
    message = _("Need to be admin in order to create queue called default")


class DefaultQueueAlreadyExists(nexception.InUse):
    message = _("Default queue already exists.")


class QueueInvalidDscp(nexception.InvalidInput):
    message = _("Invalid value for dscp %(data)s must be integer value"
                " between 0 and 63.")


class QueueInvalidMarking(nexception.InvalidInput):
    message = _("The qos marking cannot be set to 'trusted' "
                "when the DSCP field is set")


class QueueMinGreaterMax(nexception.InvalidInput):
    message = _("Invalid bandwidth rate, min greater than max.")


class QueueInvalidBandwidth(nexception.InvalidInput):
    message = _("Invalid bandwidth rate, %(data)s must be a non negative"
                " integer.")


class QueueNotFound(nexception.NotFound):
    message = _("Queue %(id)s does not exist")


class QueueInUseByPort(nexception.InUse):
    message = _("Unable to delete queue attached to port.")


class QueuePortBindingNotFound(nexception.NotFound):
    message = _("Port is not associated with lqueue")


def convert_to_unsigned_int_or_none(val):
    if val is None:
        return
    try:
        val = int(val)
        if val < 0:
            raise ValueError()
    except (ValueError, TypeError):
        msg = _("'%s' must be a non negative integer.") % val
        raise nexception.InvalidInput(error_message=msg)
    return val


def convert_to_unsigned_int_or_none_max_63(val):
    val = convert_to_unsigned_int_or_none(val)
    if val > 63:
        raise QueueInvalidDscp(data=val)
    return val

# As per NSX API, if a queue is trusted, DSCP must be omitted; if a queue is
# untrusted, DSCP must be specified. Whichever default values we choose for
# the tuple (qos_marking, dscp), there will be at least one combination of a
# request with conflicting values: for instance given the default values below,
# requests with qos_marking = 'trusted' and the default dscp value will fail.
# In order to avoid API users to explicitly specify a setting for clearing
# the DSCP field when a trusted queue is created, the code serving this API
# will adopt the following behaviour when qos_marking is set to 'trusted':
# - if the DSCP attribute is set to the default value (0), silently drop
#   its value
# - if the DSCP attribute is set to anything than 0 (but still a valid DSCP
#   value) return a 400 error as qos_marking and DSCP setting conflict.
# TODO(salv-orlando): Evaluate whether it will be possible from a backward
# compatibility perspective to change the default value for DSCP in order to
# avoid this peculiar behaviour

RESOURCE_ATTRIBUTE_MAP = {
    'qos_queues': {
        'id': {'allow_post': False, 'allow_put': False,
               'is_visible': True},
        'default': {'allow_post': True, 'allow_put': False,
                    'convert_to': converters.convert_to_boolean,
                    'is_visible': True, 'default': False},
        'name': {'allow_post': True, 'allow_put': False,
                 'validate': {'type:string': db_const.NAME_FIELD_SIZE},
                 'is_visible': True, 'default': ''},
        'min': {'allow_post': True, 'allow_put': False,
                'is_visible': True, 'default': '0',
                'convert_to': convert_to_unsigned_int_or_none},
        'max': {'allow_post': True, 'allow_put': False,
                'is_visible': True, 'default': None,
                'convert_to': convert_to_unsigned_int_or_none},
        'qos_marking': {'allow_post': True, 'allow_put': False,
                        'validate': {'type:values': ['untrusted', 'trusted']},
                        'default': 'untrusted', 'is_visible': True},
        'dscp': {'allow_post': True, 'allow_put': False,
                 'is_visible': True, 'default': '0',
                 'convert_to': convert_to_unsigned_int_or_none_max_63},
        'tenant_id': {'allow_post': True, 'allow_put': False,
                      'required_by_policy': True,
                      'validate': {
                          'type:string': db_const.PROJECT_ID_FIELD_SIZE},
                      'is_visible': True},
    },
}


QUEUE = 'queue_id'
RXTX_FACTOR = 'rxtx_factor'
EXTENDED_ATTRIBUTES_2_0 = {
    'ports': {
        RXTX_FACTOR: {'allow_post': True,
                      # FIXME(arosen): the plugin currently does not
                      # implement updating rxtx factor on port.
                      'allow_put': True,
                      'is_visible': False,
                      'default': 1,
                      'enforce_policy': True,
                      'convert_to':
                          converters.convert_to_positive_float_or_none},

        QUEUE: {'allow_post': False,
                'allow_put': False,
                'is_visible': True,
                'default': False,
                'enforce_policy': True}},
    'networks': {QUEUE: {'allow_post': True,
                         'allow_put': True,
                         'is_visible': True,
                         'default': False,
                         'enforce_policy': True}}

}


class Qos_queue(api_extensions.ExtensionDescriptor):
    """Port Queue extension."""

    @classmethod
    def get_name(cls):
        return "QoS Queue"

    @classmethod
    def get_alias(cls):
        return "qos-queue"

    @classmethod
    def get_description(cls):
        return "NSX QoS extension."

    @classmethod
    def get_updated(cls):
        return "2014-01-01T00:00:00-00:00"

    @classmethod
    def get_resources(cls):
        """Returns Ext Resources."""
        exts = []
        plugin = directory.get_plugin()
        resource_name = 'qos_queue'
        collection_name = resource_name.replace('_', '-') + "s"
        params = RESOURCE_ATTRIBUTE_MAP.get(resource_name + "s", dict())
        controller = base.create_resource(collection_name,
                                          resource_name,
                                          plugin, params, allow_bulk=False)

        ex = extensions.ResourceExtension(collection_name,
                                          controller)
        exts.append(ex)

        return exts

    def get_extended_resources(self, version):
        if version == "2.0":
            return dict(list(EXTENDED_ATTRIBUTES_2_0.items()) +
                        list(RESOURCE_ATTRIBUTE_MAP.items()))
        else:
            return {}


class QueuePluginBase(object):
    @abc.abstractmethod
    def create_qos_queue(self, context, queue):
        pass

    @abc.abstractmethod
    def delete_qos_queue(self, context, id):
        pass

    @abc.abstractmethod
    def get_qos_queue(self, context, id, fields=None):
        pass

    @abc.abstractmethod
    def get_qos_queues(self, context, filters=None, fields=None, sorts=None,
                       limit=None, marker=None, page_reverse=False):
        pass
