#
# Copyright (c) 2022 Pim van Pelt
#
# 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.
#
""" A vppcfg configuration module that validates taps """
import logging
from config import mac


def get_taps(yaml):
    """Return a list of all taps."""
    ret = []
    if "taps" in yaml:
        for ifname, _iface in yaml["taps"].items():
            ret.append(ifname)
    return ret


def get_by_name(yaml, ifname):
    """Return the tap by name, if it exists. Return None otherwise."""
    try:
        if ifname in yaml["taps"]:
            return ifname, yaml["taps"][ifname]
    except KeyError:
        pass
    return None, None


def is_tap(yaml, ifname):
    """Returns True if the interface name is an existing tap in the config.
    The TAP has to be explicitly named in the configuration, and notably
    a TAP belonging to a Linux Control Plane (LCP) will return False.
    """
    ifname, iface = get_by_name(yaml, ifname)
    return iface is not None


def is_host_name_unique(yaml, hostname):
    """Returns True if there is at most one occurence of the given ifname amonst all host-names of TAPs."""
    if not "taps" in yaml:
        return True
    host_names = []
    for _tap_ifname, tap_iface in yaml["taps"].items():
        host_names.append(tap_iface["host"]["name"])
    return host_names.count(hostname) < 2


def validate_taps(yaml):
    """Validate the semantics of all YAML 'taps' entries"""
    result = True
    msgs = []
    logger = logging.getLogger("vppcfg.config")
    logger.addHandler(logging.NullHandler())

    if not "taps" in yaml:
        return result, msgs

    for ifname, iface in yaml["taps"].items():
        logger.debug(f"tap {iface}")
        instance = int(ifname[3:])

        ## NOTE(pim): 1024 is not off-by-one, tap1024 is precisely the highest permissible id
        if instance > 1024:
            msgs.append(f"tap {ifname} has instance {int(instance)} which is too large")
            result = False

        if not is_host_name_unique(yaml, iface["host"]["name"]):
            msgs.append(
                f"tap {ifname} does not have a unique host name {iface['host']['name']}"
            )
            result = False

        if "rx-ring-size" in iface:
            ncount = iface["rx-ring-size"]
            if ncount & (ncount - 1) != 0:
                msgs.append(f"tap {ifname} rx-ring-size must be a power of two")
                result = False

        if "tx-ring-size" in iface:
            ncount = iface["tx-ring-size"]
            if ncount & (ncount - 1) != 0:
                msgs.append(f"tap {ifname} tx-ring-size must be a power of two")
                result = False

        if (
            "namespace-create" in iface["host"]
            and iface["host"]["namespace-create"]
            and not "namespace" in iface["host"]
        ):
            msgs.append(
                f"tap {ifname} namespace-create can only be set if namespace is set"
            )
            result = False

        if (
            "bridge-create" in iface["host"]
            and iface["host"]["bridge-create"]
            and not "bridge" in iface["host"]
        ):
            msgs.append(f"tap {ifname} bridge-create can only be set if bridge is set")
            result = False

        if "mac" in iface["host"] and mac.is_multicast(iface["host"]["mac"]):
            msgs.append(
                f"tap {ifname} host MAC address {iface['host']['mac']} cannot be multicast"
            )
            result = False

    return result, msgs