# Copyright (C) 2014 Nippon Telegraph and Telephone Corporation.
#
# 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.

"""
 Running or runtime configuration base classes.
"""
import abc
from abc import ABCMeta
from abc import abstractmethod
import functools
import numbers
import logging
import uuid

from os_ken.services.protocols.bgp.base import add_bgp_error_metadata
from os_ken.services.protocols.bgp.base import BGPSException
from os_ken.services.protocols.bgp.base import get_validator
from os_ken.services.protocols.bgp.base import RUNTIME_CONF_ERROR_CODE
from os_ken.services.protocols.bgp.base import validate
from os_ken.services.protocols.bgp.utils import validation
from os_ken.services.protocols.bgp.utils.validation import is_valid_asn

LOG = logging.getLogger('bgpspeaker.rtconf.base')

#
# Nested settings.
#
CAP_REFRESH = 'cap_refresh'
CAP_ENHANCED_REFRESH = 'cap_enhanced_refresh'
CAP_FOUR_OCTET_AS_NUMBER = 'cap_four_octet_as_number'
CAP_MBGP_IPV4 = 'cap_mbgp_ipv4'
CAP_MBGP_IPV6 = 'cap_mbgp_ipv6'
CAP_MBGP_VPNV4 = 'cap_mbgp_vpnv4'
CAP_MBGP_VPNV6 = 'cap_mbgp_vpnv6'
CAP_MBGP_EVPN = 'cap_mbgp_evpn'
CAP_MBGP_IPV4FS = 'cap_mbgp_ipv4fs'
CAP_MBGP_IPV6FS = 'cap_mbgp_ipv6fs'
CAP_MBGP_VPNV4FS = 'cap_mbgp_vpnv4fs'
CAP_MBGP_VPNV6FS = 'cap_mbgp_vpnv6fs'
CAP_MBGP_L2VPNFS = 'cap_mbgp_l2vpnfs'
CAP_RTC = 'cap_rtc'
RTC_AS = 'rtc_as'
HOLD_TIME = 'hold_time'

# To control how many prefixes can be received from a neighbor.
# 0 value indicates no limit and other related options will be ignored.
# Current behavior is to log that limit has reached.
MAX_PREFIXES = 'max_prefixes'

# Has same meaning as: http://www.juniper.net/techpubs/software/junos/junos94
# /swconfig-routing/disabling-suppression-of-route-
# advertisements.html#id-13255463
ADVERTISE_PEER_AS = 'advertise_peer_as'

# MED - MULTI_EXIT_DISC
MULTI_EXIT_DISC = 'multi_exit_disc'

# Extended community attribute route origin.
SITE_OF_ORIGINS = 'site_of_origins'

# Constants related to errors.
CONF_NAME = 'conf_name'
CONF_VALUE = 'conf_value'

# Max. value  limits
MAX_NUM_IMPORT_RT = 1000
MAX_NUM_EXPORT_RT = 250
MAX_NUM_SOO = 10


# =============================================================================
# Runtime configuration errors or exceptions.
# =============================================================================

@add_bgp_error_metadata(code=RUNTIME_CONF_ERROR_CODE, sub_code=1,
                        def_desc='Error with runtime-configuration.')
class RuntimeConfigError(BGPSException):
    """Base class for all runtime configuration errors.
    """
    pass


@add_bgp_error_metadata(code=RUNTIME_CONF_ERROR_CODE, sub_code=2,
                        def_desc='Missing required configuration.')
class MissingRequiredConf(RuntimeConfigError):
    """Exception raised when trying to configure with missing required
    settings.
    """

    def __init__(self, **kwargs):
        conf_name = kwargs.get('conf_name')
        if conf_name:
            super(MissingRequiredConf, self).__init__(
                desc='Missing required configuration: %s' % conf_name)
        else:
            super(MissingRequiredConf, self).__init__(desc=kwargs.get('desc'))


@add_bgp_error_metadata(code=RUNTIME_CONF_ERROR_CODE, sub_code=3,
                        def_desc='Incorrect Type for configuration.')
class ConfigTypeError(RuntimeConfigError):
    """Exception raised when configuration value type miss-match happens.
    """

    def __init__(self, **kwargs):
        conf_name = kwargs.get(CONF_NAME)
        conf_value = kwargs.get(CONF_VALUE)
        if conf_name and conf_value:
            super(ConfigTypeError, self).__init__(
                desc='Incorrect Type %s for configuration: %s' %
                (conf_value, conf_name))
        elif conf_name:
            super(ConfigTypeError, self).__init__(
                desc='Incorrect Type for configuration: %s' % conf_name)
        else:
            super(ConfigTypeError, self).__init__(desc=kwargs.get('desc'))


@add_bgp_error_metadata(code=RUNTIME_CONF_ERROR_CODE, sub_code=4,
                        def_desc='Incorrect Value for configuration.')
class ConfigValueError(RuntimeConfigError):
    """Exception raised when configuration value is of correct type but
    incorrect value.
    """

    def __init__(self, **kwargs):
        conf_name = kwargs.get(CONF_NAME)
        conf_value = kwargs.get(CONF_VALUE)
        if conf_name and conf_value:
            super(ConfigValueError, self).__init__(
                desc='Incorrect Value %s for configuration: %s' %
                (conf_value, conf_name))
        elif conf_name:
            super(ConfigValueError, self).__init__(
                desc='Incorrect Value for configuration: %s' % conf_name)
        else:
            super(ConfigValueError, self).__init__(desc=kwargs.get('desc'))


# =============================================================================
# Configuration base classes.
# =============================================================================

class BaseConf(object, metaclass=abc.ABCMeta):
    """Base class for a set of configuration values.

    Configurations can be required or optional. Also acts as a container of
    configuration change listeners.
    """

    def __init__(self, **kwargs):
        self._req_settings = self.get_req_settings()
        self._opt_settings = self.get_opt_settings()
        self._valid_evts = self.get_valid_evts()
        self._listeners = {}
        self._settings = {}

        # validate required and unknown settings
        self._validate_req_unknown_settings(**kwargs)

        # Initialize configuration settings.
        self._init_req_settings(**kwargs)
        self._init_opt_settings(**kwargs)

    @property
    def settings(self):
        """Returns a copy of current settings."""
        return self._settings.copy()

    @classmethod
    def get_valid_evts(cls):
        return set()

    @classmethod
    def get_req_settings(cls):
        return set()

    @classmethod
    def get_opt_settings(cls):
        return set()

    @abstractmethod
    def _init_opt_settings(self, **kwargs):
        """Sub-classes should override this method to initialize optional
         settings.
        """
        pass

    @abstractmethod
    def update(self, **kwargs):
        # Validate given values
        self._validate_req_unknown_settings(**kwargs)

    def _validate_req_unknown_settings(self, **kwargs):
        """Checks if required settings are present.

        Also checks if unknown requirements are present.
        """
        # Validate given configuration.
        self._all_attrs = (self._req_settings | self._opt_settings)
        if not kwargs and len(self._req_settings) > 0:
            raise MissingRequiredConf(desc='Missing all required attributes.')

        given_attrs = frozenset(kwargs.keys())
        unknown_attrs = given_attrs - self._all_attrs
        if unknown_attrs:
            raise RuntimeConfigError(desc=(
                'Unknown attributes: %s' %
                ', '.join([str(i) for i in unknown_attrs])))
        missing_req_settings = self._req_settings - given_attrs
        if missing_req_settings:
            raise MissingRequiredConf(conf_name=list(missing_req_settings))

    def _init_req_settings(self, **kwargs):
        for req_attr in self._req_settings:
            req_attr_value = kwargs.get(req_attr)
            if req_attr_value is None:
                raise MissingRequiredConf(conf_name=req_attr_value)
            # Validate attribute value
            req_attr_value = get_validator(req_attr)(req_attr_value)
            self._settings[req_attr] = req_attr_value

    def add_listener(self, evt, callback):
        #   if (evt not in self.get_valid_evts()):
        #       raise RuntimeConfigError(desc=('Unknown event %s' % evt))

        listeners = self._listeners.get(evt, None)
        if not listeners:
            listeners = set()
            self._listeners[evt] = listeners
        listeners.update([callback])

    def remove_listener(self, evt, callback):
        if evt in self.get_valid_evts():
            listeners = self._listeners.get(evt, None)
            if listeners and (callback in listeners):
                listeners.remove(callback)
                return True

        return False

    def _notify_listeners(self, evt, value):
        listeners = self._listeners.get(evt, [])
        for callback in listeners:
            callback(ConfEvent(self, evt, value))

    def __repr__(self):
        return '%s(%r)' % (self.__class__, self._settings)


class ConfWithId(BaseConf):
    """Configuration settings related to identity."""
    # Config./resource identifier.
    ID = 'id'
    # Config./resource name.
    NAME = 'name'
    # Config./resource description.
    DESCRIPTION = 'description'

    UPDATE_NAME_EVT = 'update_name_evt'
    UPDATE_DESCRIPTION_EVT = 'update_description_evt'

    VALID_EVT = frozenset([UPDATE_NAME_EVT, UPDATE_DESCRIPTION_EVT])
    OPTIONAL_SETTINGS = frozenset([ID, NAME, DESCRIPTION])

    def __init__(self, **kwargs):
        super(ConfWithId, self).__init__(**kwargs)

    @classmethod
    def get_opt_settings(cls):
        self_confs = super(ConfWithId, cls).get_opt_settings()
        self_confs.update(ConfWithId.OPTIONAL_SETTINGS)
        return self_confs

    @classmethod
    def get_req_settings(cls):
        self_confs = super(ConfWithId, cls).get_req_settings()
        return self_confs

    @classmethod
    def get_valid_evts(cls):
        self_valid_evts = super(ConfWithId, cls).get_valid_evts()
        self_valid_evts.update(ConfWithId.VALID_EVT)
        return self_valid_evts

    def _init_opt_settings(self, **kwargs):
        super(ConfWithId, self)._init_opt_settings(**kwargs)
        self._settings[ConfWithId.ID] = \
            compute_optional_conf(ConfWithId.ID, str(uuid.uuid4()), **kwargs)
        self._settings[ConfWithId.NAME] = \
            compute_optional_conf(ConfWithId.NAME, str(self), **kwargs)
        self._settings[ConfWithId.DESCRIPTION] = \
            compute_optional_conf(ConfWithId.DESCRIPTION, str(self), **kwargs)

    @property
    def id(self):
        return self._settings[ConfWithId.ID]

    @property
    def name(self):
        return self._settings[ConfWithId.NAME]

    @name.setter
    def name(self, new_name):
        old_name = self.name
        if not new_name:
            new_name = repr(self)
        else:
            get_validator(ConfWithId.NAME)(new_name)

        if old_name != new_name:
            self._settings[ConfWithId.NAME] = new_name
            self._notify_listeners(ConfWithId.UPDATE_NAME_EVT,
                                   (old_name, self.name))

    @property
    def description(self):
        return self._settings[ConfWithId.DESCRIPTION]

    @description.setter
    def description(self, new_description):
        old_desc = self.description
        if not new_description:
            new_description = str(self)
        else:
            get_validator(ConfWithId.DESCRIPTION)(new_description)

        if old_desc != new_description:
            self._settings[ConfWithId.DESCRIPTION] = new_description
            self._notify_listeners(ConfWithId.UPDATE_DESCRIPTION_EVT,
                                   (old_desc, self.description))

    def update(self, **kwargs):
        # Update inherited configurations
        super(ConfWithId, self).update(**kwargs)
        self.name = compute_optional_conf(ConfWithId.NAME,
                                          str(self),
                                          **kwargs)
        self.description = compute_optional_conf(ConfWithId.DESCRIPTION,
                                                 str(self),
                                                 **kwargs)


class ConfWithStats(BaseConf):
    """Configuration settings related to statistics collection."""

    # Enable or disable statistics logging.
    STATS_LOG_ENABLED = 'statistics_log_enabled'
    DEFAULT_STATS_LOG_ENABLED = False

    # Statistics logging time.
    STATS_TIME = 'statistics_interval'
    DEFAULT_STATS_TIME = 60

    UPDATE_STATS_LOG_ENABLED_EVT = 'update_stats_log_enabled_evt'
    UPDATE_STATS_TIME_EVT = 'update_stats_time_evt'

    VALID_EVT = frozenset([UPDATE_STATS_LOG_ENABLED_EVT,
                           UPDATE_STATS_TIME_EVT])
    OPTIONAL_SETTINGS = frozenset([STATS_LOG_ENABLED, STATS_TIME])

    def __init__(self, **kwargs):
        super(ConfWithStats, self).__init__(**kwargs)

    def _init_opt_settings(self, **kwargs):
        super(ConfWithStats, self)._init_opt_settings(**kwargs)
        self._settings[ConfWithStats.STATS_LOG_ENABLED] = \
            compute_optional_conf(ConfWithStats.STATS_LOG_ENABLED,
                                  ConfWithStats.DEFAULT_STATS_LOG_ENABLED,
                                  **kwargs)
        self._settings[ConfWithStats.STATS_TIME] = \
            compute_optional_conf(ConfWithStats.STATS_TIME,
                                  ConfWithStats.DEFAULT_STATS_TIME,
                                  **kwargs)

    @property
    def stats_log_enabled(self):
        return self._settings[ConfWithStats.STATS_LOG_ENABLED]

    @stats_log_enabled.setter
    def stats_log_enabled(self, enabled):
        get_validator(ConfWithStats.STATS_LOG_ENABLED)(enabled)
        if enabled != self.stats_log_enabled:
            self._settings[ConfWithStats.STATS_LOG_ENABLED] = enabled
            self._notify_listeners(ConfWithStats.UPDATE_STATS_LOG_ENABLED_EVT,
                                   enabled)

    @property
    def stats_time(self):
        return self._settings[ConfWithStats.STATS_TIME]

    @stats_time.setter
    def stats_time(self, stats_time):
        get_validator(ConfWithStats.STATS_TIME)(stats_time)
        if stats_time != self.stats_time:
            self._settings[ConfWithStats.STATS_TIME] = stats_time
            self._notify_listeners(ConfWithStats.UPDATE_STATS_TIME_EVT,
                                   stats_time)

    @classmethod
    def get_opt_settings(cls):
        confs = super(ConfWithStats, cls).get_opt_settings()
        confs.update(ConfWithStats.OPTIONAL_SETTINGS)
        return confs

    @classmethod
    def get_valid_evts(cls):
        valid_evts = super(ConfWithStats, cls).get_valid_evts()
        valid_evts.update(ConfWithStats.VALID_EVT)
        return valid_evts

    def update(self, **kwargs):
        # Update inherited configurations
        super(ConfWithStats, self).update(**kwargs)
        self.stats_log_enabled = \
            compute_optional_conf(ConfWithStats.STATS_LOG_ENABLED,
                                  ConfWithStats.DEFAULT_STATS_LOG_ENABLED,
                                  **kwargs)
        self.stats_time = \
            compute_optional_conf(ConfWithStats.STATS_TIME,
                                  ConfWithStats.DEFAULT_STATS_TIME,
                                  **kwargs)


class BaseConfListener(object, metaclass=abc.ABCMeta):
    """Base class of all configuration listeners."""

    def __init__(self, base_conf):
        pass
    # TODO(PH): re-vist later and check if we need this check
#         if not isinstance(base_conf, BaseConf):
#             raise TypeError('Currently we only support listening to '
#                             'instances of BaseConf')


class ConfWithIdListener(BaseConfListener):

    def __init__(self, conf_with_id):
        assert conf_with_id
        super(ConfWithIdListener, self).__init__(conf_with_id)
        conf_with_id.add_listener(ConfWithId.UPDATE_NAME_EVT,
                                  self.on_chg_name_conf_with_id)
        conf_with_id.add_listener(ConfWithId.UPDATE_DESCRIPTION_EVT,
                                  self.on_chg_desc_conf_with_id)

    def on_chg_name_conf_with_id(self, conf_evt):
        # Note did not makes this method abstract as this is not important
        # event.
        raise NotImplementedError()

    def on_chg_desc_conf_with_id(self, conf_evt):
        # Note did not makes this method abstract as this is not important
        # event.
        raise NotImplementedError()


class ConfWithStatsListener(BaseConfListener):

    def __init__(self, conf_with_stats):
        assert conf_with_stats
        super(ConfWithStatsListener, self).__init__(conf_with_stats)
        conf_with_stats.add_listener(
            ConfWithStats.UPDATE_STATS_LOG_ENABLED_EVT,
            self.on_chg_stats_enabled_conf_with_stats)

        conf_with_stats.add_listener(ConfWithStats.UPDATE_STATS_TIME_EVT,
                                     self.on_chg_stats_time_conf_with_stats)

    @abstractmethod
    def on_chg_stats_time_conf_with_stats(self, conf_evt):
        raise NotImplementedError()

    @abstractmethod
    def on_chg_stats_enabled_conf_with_stats(self, conf_evt):
        raise NotImplementedError()


@functools.total_ordering
class ConfEvent(object):
    """Encapsulates configuration settings change/update event."""

    def __init__(self, evt_src, evt_name, evt_value):
        """Creates an instance using given parameters.

        Parameters:
            -`evt_src`: (BaseConf) source of the event
            -`evt_name`: (str) name of event, has to be one of the valid
            event of `evt_src`
            - `evt_value`: (tuple) event context that helps event handler
        """
        if evt_name not in evt_src.get_valid_evts():
            raise ValueError('Event %s is not a valid event for type %s.' %
                             (evt_name, type(evt_src)))
        self._src = evt_src
        self._name = evt_name
        self._value = evt_value

    @property
    def src(self):
        return self._src

    @property
    def name(self):
        return self._name

    @property
    def value(self):
        return self._value

    def __repr__(self):
        return '<ConfEvent(%s, %s, %s)>' % (self.src, self.name, self.value)

    def __str__(self):
        return ('ConfEvent(src=%s, name=%s, value=%s)' %
                (self.src, self.name, self.value))

    def __lt__(self, other):
        return ((self.src, self.name, self.value) <
                (other.src, other.name, other.value))

    def __eq__(self, other):
        return ((self.src, self.name, self.value) ==
                (other.src, other.name, other.value))


# =============================================================================
# Runtime configuration setting validators and their registry.
# =============================================================================

@validate(name=ConfWithId.ID)
def validate_conf_id(identifier):
    if not isinstance(identifier, str):
        raise ConfigTypeError(conf_name=ConfWithId.ID, conf_value=identifier)
    if len(identifier) > 128:
        raise ConfigValueError(conf_name=ConfWithId.ID, conf_value=identifier)
    return identifier


@validate(name=ConfWithId.NAME)
def validate_conf_name(name):
    if not isinstance(name, str):
        raise ConfigTypeError(conf_name=ConfWithId.NAME, conf_value=name)
    if len(name) > 128:
        raise ConfigValueError(conf_name=ConfWithId.NAME, conf_value=name)
    return name


@validate(name=ConfWithId.DESCRIPTION)
def validate_conf_desc(description):
    if not isinstance(description, str):
        raise ConfigTypeError(conf_name=ConfWithId.DESCRIPTION,
                              conf_value=description)
    return description


@validate(name=ConfWithStats.STATS_LOG_ENABLED)
def validate_stats_log_enabled(stats_log_enabled):
    if not isinstance(stats_log_enabled, bool):
        raise ConfigTypeError(desc='Statistics log enabled settings can only'
                              ' be boolean type.')
    return stats_log_enabled


@validate(name=ConfWithStats.STATS_TIME)
def validate_stats_time(stats_time):
    if not isinstance(stats_time, numbers.Integral):
        raise ConfigTypeError(desc='Statistics log timer value has to be of '
                              'integral type but got: %r' % stats_time)
    if stats_time < 10:
        raise ConfigValueError(desc='Statistics log timer cannot be set to '
                               'less then 10 sec, given timer value %s.' %
                               stats_time)
    return stats_time


@validate(name=CAP_REFRESH)
def validate_cap_refresh(crefresh):
    if not isinstance(crefresh, bool):
        raise ConfigTypeError(desc='Invalid Refresh capability settings: %s. '
                              'Boolean value expected' % crefresh)
    return crefresh


@validate(name=CAP_ENHANCED_REFRESH)
def validate_cap_enhanced_refresh(cer):
    if not isinstance(cer, bool):
        raise ConfigTypeError(desc='Invalid Enhanced Refresh capability '
                              'settings: %s. Boolean value expected' % cer)
    return cer


@validate(name=CAP_FOUR_OCTET_AS_NUMBER)
def validate_cap_four_octet_as_number(cfoan):
    if not isinstance(cfoan, bool):
        raise ConfigTypeError(desc='Invalid Four-Octet AS Number capability '
                              'settings: %s boolean value expected' % cfoan)
    return cfoan


@validate(name=CAP_MBGP_IPV4)
def validate_cap_mbgp_ipv4(cmv4):
    if not isinstance(cmv4, bool):
        raise ConfigTypeError(desc='Invalid MP-BGP IPv4 capability '
                              'settings: %s. Boolean value expected' % cmv4)

    return cmv4


@validate(name=CAP_MBGP_IPV6)
def validate_cap_mbgp_ipv6(cmv6):
    if not isinstance(cmv6, bool):
        raise ConfigTypeError(desc='Invalid MP-BGP IPv6 capability '
                              'settings: %s. Boolean value expected' % cmv6)

    return cmv6


@validate(name=CAP_MBGP_VPNV4)
def validate_cap_mbgp_vpnv4(cmv4):
    if not isinstance(cmv4, bool):
        raise ConfigTypeError(desc='Invalid MP-BGP VPNv4 capability '
                              'settings: %s. Boolean value expected' % cmv4)

    return cmv4


@validate(name=CAP_MBGP_VPNV6)
def validate_cap_mbgp_vpnv6(cmv6):
    if not isinstance(cmv6, bool):
        raise ConfigTypeError(desc='Invalid MP-BGP VPNv6 capability '
                              'settings: %s. Boolean value expected' % cmv6)

    return cmv6


@validate(name=CAP_MBGP_EVPN)
def validate_cap_mbgp_evpn(cmevpn):
    if not isinstance(cmevpn, bool):
        raise ConfigTypeError(desc='Invalid Ethernet VPN capability '
                              'settings: %s. Boolean value expected' % cmevpn)
    return cmevpn


@validate(name=CAP_MBGP_IPV4FS)
def validate_cap_mbgp_ipv4fs(cmv4fs):
    if not isinstance(cmv4fs, bool):
        raise ConfigTypeError(desc='Invalid MP-BGP '
                              'IPv4 Flow Specification capability '
                              'settings: %s. Boolean value expected' % cmv4fs)
    return cmv4fs


@validate(name=CAP_MBGP_IPV6FS)
def validate_cap_mbgp_ipv6fs(cmv6fs):
    if not isinstance(cmv6fs, bool):
        raise ConfigTypeError(desc='Invalid MP-BGP '
                              'IPv6 Flow Specification capability '
                              'settings: %s. Boolean value expected' % cmv6fs)
    return cmv6fs


@validate(name=CAP_MBGP_VPNV4FS)
def validate_cap_mbgp_vpnv4fs(cmv4fs):
    if not isinstance(cmv4fs, bool):
        raise ConfigTypeError(desc='Invalid MP-BGP '
                              'VPNv4 Flow Specification capability '
                              'settings: %s. Boolean value expected' % cmv4fs)
    return cmv4fs


@validate(name=CAP_MBGP_VPNV6FS)
def validate_cap_mbgp_vpnv66fs(cmv6fs):
    if not isinstance(cmv6fs, bool):
        raise ConfigTypeError(desc='Invalid MP-BGP '
                              'VPNv6 Flow Specification capability '
                              'settings: %s. Boolean value expected' % cmv6fs)
    return cmv6fs


@validate(name=CAP_MBGP_L2VPNFS)
def validate_cap_mbgp_l2vpnfs(cml2fs):
    if not isinstance(cml2fs, bool):
        raise ConfigTypeError(desc='Invalid MP-BGP '
                              'L2VPN Flow Specification capability '
                              'settings: %s. Boolean value expected' % cml2fs)
    return cml2fs


@validate(name=CAP_RTC)
def validate_cap_rtc(cap_rtc):
    if not isinstance(cap_rtc, bool):
        raise ConfigTypeError(desc='Invalid type for specifying RTC '
                              'capability. Expected boolean got: %s' %
                              type(cap_rtc))
    return cap_rtc


@validate(name=RTC_AS)
def validate_cap_rtc_as(rtc_as):
    if not is_valid_asn(rtc_as):
        raise ConfigValueError(desc='Invalid RTC AS configuration value: %s'
                               % rtc_as)
    return rtc_as


@validate(name=HOLD_TIME)
def validate_hold_time(hold_time):
    if ((hold_time is None) or (not isinstance(hold_time, int)) or
            hold_time < 10):
        raise ConfigValueError(desc='Invalid hold_time configuration value %s'
                               % hold_time)

    return hold_time


@validate(name=MULTI_EXIT_DISC)
def validate_med(med):
    if med is not None and not validation.is_valid_med(med):
        raise ConfigValueError(desc='Invalid multi-exit-discriminatory (med)'
                               ' value: %s.' % med)
    return med


@validate(name=SITE_OF_ORIGINS)
def validate_soo_list(soo_list):
    if not isinstance(soo_list, list):
        raise ConfigTypeError(conf_name=SITE_OF_ORIGINS, conf_value=soo_list)
    if len(soo_list) > MAX_NUM_SOO:
        raise ConfigValueError(desc='Max. SOO is limited to %s' %
                               MAX_NUM_SOO)
    if not all(validation.is_valid_ext_comm_attr(attr) for attr in soo_list):
        raise ConfigValueError(conf_name=SITE_OF_ORIGINS,
                               conf_value=soo_list)
    # Check if we have duplicates
    unique_rts = set(soo_list)
    if len(unique_rts) != len(soo_list):
        raise ConfigValueError(desc='Duplicate value provided in %s' %
                               soo_list)
    return soo_list


@validate(name=MAX_PREFIXES)
def validate_max_prefixes(max_prefixes):
    if not isinstance(max_prefixes, int):
        raise ConfigTypeError(desc='Max. prefixes value should be of type '
                              'int or long but found %s' % type(max_prefixes))
    if max_prefixes < 0:
        raise ConfigValueError(desc='Invalid max. prefixes value: %s' %
                               max_prefixes)
    return max_prefixes


@validate(name=ADVERTISE_PEER_AS)
def validate_advertise_peer_as(advertise_peer_as):
    if not isinstance(advertise_peer_as, bool):
        raise ConfigTypeError(desc='Invalid type for advertise-peer-as, '
                              'expected bool got %s' %
                              type(advertise_peer_as))
    return advertise_peer_as


# =============================================================================
# Other utils.
# =============================================================================

def compute_optional_conf(conf_name, default_value, **all_config):
    """Returns *conf_name* settings if provided in *all_config*, else returns
     *default_value*.

    Validates *conf_name* value if provided.
    """
    conf_value = all_config.get(conf_name)
    if conf_value is not None:
        # Validate configuration value.
        conf_value = get_validator(conf_name)(conf_value)
    else:
        conf_value = default_value
    return conf_value
