# Copyright (c) 2021 Cisco and/or its affiliates.
# 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.

"""VPP GENEVE Plugin utilities library."""

from ipaddress import ip_address

from resources.libraries.python.Constants import Constants
from resources.libraries.python.InterfaceUtil import InterfaceUtil
from resources.libraries.python.IPAddress import IPAddress
from resources.libraries.python.IPUtil import IPUtil
from resources.libraries.python.PapiExecutor import PapiSocketExecutor
from resources.libraries.python.topology import Topology


class GeneveUtil:
    """VPP GENEVE Plugin Keywords."""

    @staticmethod
    def add_geneve_tunnel(
            node, local_address, remote_address, vni, multicast_if=None,
            encap_vrf=0, l3_mode=False, next_index=None):
        """Add GENEVE tunnel on the specified VPP node.

        :param node: Topology node.
        :param local_address: Local IP address.
        :param remote_address: Remote IP address.
        :param vni: Virtual network ID.
        :param multicast_if: Interface key of multicast interface; used only if
            remote is multicast. (Default value = None)
        :param encap_vrf: The FIB ID for sending unicast GENEVE encap packets or
            receiving multicast packets. (Default value = 0)
        :param l3_mode: Use geneve tunnel in L3 mode (ip routing) if Tue else in
            L2 mode (L2 switching). (Default value = False)
        :param next_index: The index of the next node.
        :type node: dict
        :type local_address: str
        :type remote_address: str
        :type vni: int
        :type multicast_if: str
        :type encap_vrf: int
        :type l3_mode: bool
        :type next_index: int
        :returns: SW interface index of created geneve tunnel.
        :rtype: int
        """
        cmd = u"geneve_add_del_tunnel2"
        args = dict(
            is_add=True,
            local_address=IPAddress.create_ip_address_object(
                ip_address(local_address)
            ),
            remote_address=IPAddress.create_ip_address_object(
                ip_address(remote_address)
            ),
            mcast_sw_if_index=Topology.get_interface_sw_index(
                node, multicast_if
            ) if multicast_if else Constants.BITWISE_NON_ZERO,
            encap_vrf_id=int(encap_vrf),
            decap_next_index=next_index if l3_mode
            else Constants.BITWISE_NON_ZERO,
            vni=int(vni),
            l3_mode=l3_mode
        )
        err_msg = f"Failed to configure GENEVE tunnel on host {node[u'host']}!"
        with PapiSocketExecutor(node) as papi_exec:
            sw_if_index = papi_exec.add(cmd, **args).get_sw_if_index(err_msg)

        if_key = Topology.add_new_port(node, u"geneve_tunnel")
        Topology.update_interface_sw_if_index(node, if_key, sw_if_index)

        ifc_name = InterfaceUtil.vpp_get_interface_name(node, sw_if_index)
        Topology.update_interface_name(node, if_key, ifc_name)

        ifc_mac = InterfaceUtil.vpp_get_interface_mac(node, sw_if_index)
        Topology.update_interface_mac_address(node, if_key, ifc_mac)

        return sw_if_index

    @staticmethod
    def enable_interface_geneve_bypass(node, interface, is_ipv6=False):
        """Add ipv4/ipv6-geneve-bypass graph node for a given interface on
        the specified VPP node.

        :param node: Topology node.
        :param interface: Interface key from topology file of interface
            to add geneve bypass node for.
        :param is_ipv6: Enable ipv6-geneve-bypass graph node if True else enable
            ipv4-geneve-bypass graph node.
        :type node: dict
        :type interface: str
        :type is_ipv6: bool
        """
        cmd = u"sw_interface_set_geneve_bypass"
        args = dict(
            is_ipv6=is_ipv6,
            enable=True,
            sw_if_index=Topology.get_interface_sw_index(node, interface)
        )
        err_msg = (
            f"Failed to enable {u'ipv6' if is_ipv6 else u'ipv4'}-geneve-bypass "
            f"on interface {interface} on host {node[u'host']}!"
        )
        with PapiSocketExecutor(node) as papi_exec:
            papi_exec.add(cmd, **args).get_reply(err_msg)

    @staticmethod
    def show_geneve_tunnel_data(node):
        """Show the GENEVE tunnels data.

        :param node: DUT node.
        :type node: dict
        """
        cmds = [
            u"geneve_tunnel_dump",
        ]
        PapiSocketExecutor.dump_and_log(node, cmds)

    @staticmethod
    def vpp_geneve_add_multiple_tunnels(
            node, gen_tunnel, n_tunnels, dut_if1, dut_if2, tg_if1_ip4,
            tg_if2_ip4, tg_pf2_mac, next_idx):
        """Create multiple GENEVE tunnels.

        :param node: DUT node.
        :param gen_tunnel: Parameters of the GENEVE tunnel.
        :param n_tunnels: Number of tunnels.
        :param dut_if1: The first DUT interface.
        :param dut_if2: The second DUT interface.
        :param tg_if1_ip4: TG interface 1 IP address.
        :param tg_if2_ip4: TG interface 2 IP address.
        :param tg_pf2_mac: TG interface 2 MAC address.
        :param next_idx: The index of the next node.
        :type node: dict
        :type gen_tunnel: dict
        :type n_tunnels: int
        :type dut_if1: str
        :type dut_if2: str
        :type tg_if1_ip4: str
        :type tg_if2_ip4: str
        :type tg_pf2_mac: str
        :type next_idx: int
        """

        src_ip_int = IPUtil.ip_to_int(gen_tunnel[u"src_ip"])
        dst_ip_int = IPUtil.ip_to_int(gen_tunnel[u"dst_ip"])
        if_ip_int = IPUtil.ip_to_int(gen_tunnel[u"if_ip"])

        for idx in range(n_tunnels):
            src_ip = IPUtil.int_to_ip(src_ip_int + idx * 256)
            dst_ip = IPUtil.int_to_ip(dst_ip_int + idx * 256)
            if_ip = IPUtil.int_to_ip(if_ip_int + idx * 256)

            IPUtil.vpp_route_add(
                node, src_ip, gen_tunnel[u"ip_mask"],
                gateway=tg_if1_ip4, interface=dut_if1
            )
            tunnel_sw_index = GeneveUtil.add_geneve_tunnel(
                node, gen_tunnel[u"local"], gen_tunnel[u"remote"],
                gen_tunnel[u"vni"] + idx, l3_mode=True, next_index=next_idx
            )
            tunnel_if_key = Topology.get_interface_by_sw_index(
                node, tunnel_sw_index
            )
            tunnel_if_mac = Topology.get_interface_mac(
                node, tunnel_if_key
            )
            IPUtil.vpp_interface_set_ip_address(node, tunnel_if_key, if_ip, 24)
            IPUtil.vpp_add_ip_neighbor(
                node, tunnel_if_key, tg_if2_ip4, tg_pf2_mac
            )
            IPUtil.vpp_route_add(
                node, dst_ip, gen_tunnel[u"ip_mask"],
                gateway=tg_if2_ip4, interface=tunnel_if_key
            )
            IPUtil.vpp_route_add(
                node, gen_tunnel[u"remote"], 32,
                gateway=tg_if2_ip4, interface=dut_if2
            )
            IPUtil.vpp_add_ip_neighbor(
                node, tunnel_if_key, gen_tunnel[u"local"], tunnel_if_mac
            )
            IPUtil.vpp_route_add(
                node, gen_tunnel[u"local"], 32, gateway=if_ip
            )
            InterfaceUtil.set_interface_state(node, tunnel_if_key, u"up")