# Copyright (c) 2021 Huawei Technologies Co.,Ltd. All rights reserved.
#
# StratoVirt is licensed under Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan
# PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#         http:#license.coscl.org.cn/MulanPSL2
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
# KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
# NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the Mulan PSL v2 for more details.
"""global resources"""

import threading
import random
from subprocess import run
from subprocess import CalledProcessError
from utils.config import CONFIG
from utils.utils_network import generate_random_name, generate_random_mac
from utils.decorators import Singleton


class NetworkResource(Singleton):
    """Network resource"""
    tap_cmd = "ip"

    def __init__(self, bridge=CONFIG.bridge_name, nets_num=CONFIG.nets_num,
                 ip_3rd=CONFIG.ip_3rd, ip_prefix=CONFIG.ip_prefix,
                 dhcp_lower_limit=CONFIG.dhcp_lower_limit, dhcp_top_limit=CONFIG.dhcp_top_limit,
                 static_ip_lower_limit=CONFIG.static_ip_lower_limit, netmasklen=CONFIG.netmasklen,
                 static_ip_top_limit=CONFIG.static_ip_top_limit, netmask=CONFIG.netmask):
        self.bridge = bridge
        self.ip_3rd = ip_3rd
        self.ip_prefix = ip_prefix
        self.dhcp_lower_limit = dhcp_lower_limit
        self.dhcp_top_limit = dhcp_top_limit
        self.ipaddr = "%s.%s.1" % (self.ip_prefix, str(self.ip_3rd))
        self.static_ip_range = list(range(static_ip_lower_limit, static_ip_top_limit))
        self.netmasklen = netmasklen
        self.netmask = netmask
        self.lock = threading.Lock()
        self.ip_resources = dict()
        self.nets_num = nets_num

    def check_env(self):
        """Check dnsmasq process is running normal"""
        # create bridge if it does not exist
        run("brctl show %s || brctl addbr %s" % (self.bridge, self.bridge), shell=True, check=True)

        run("ip link set %s up" % self.bridge, shell=True, check=True)

        for index in range(self.nets_num):
            ipaddr = "%s.%s.1" % (self.ip_prefix, str(self.ip_3rd + index))
            run("ip addr add %s/%s dev %s" % (ipaddr, self.netmasklen, self.bridge),
                shell=True, check=False)

        # create dnsmasq to alloc ipaddr if it's not running
        _cmd = "ps -ef | grep dnsmasq | grep -w %s" % self.bridge
        _result = run(_cmd, shell=True, check=True)
        iprange_1 = "%s.%s.%s" % (self.ip_prefix, str(self.ip_3rd), str(self.dhcp_lower_limit))
        iprange_2 = "%s.%s.%s" % (self.ip_prefix, str(self.ip_3rd), str(self.dhcp_top_limit))
        if _result.returncode != 0:
            _cmd = "dnsmasq --no-hosts --no-resolv --strict-order --bind-interfaces" \
                   "--interface=%s --except-interface=lo --leasefile-ro " \
                   "--dhcp-range=%s,%s" % (self.bridge, iprange_1, iprange_2)
            _result = run(_cmd, shell=True, check=True)
            return not bool(_result.returncode)

        return True

    def generator_tap(self, create_tap=True):
        """
        Generator a tap device to vm, and link tap to bridge

        Returns:
            {"name": tapname, "mac": mac}
        """
        self.check_env()
        tapname = generate_random_name()

        if create_tap:
            try:
                _cmd = "ip tuntap add %s mode tap && brctl addif %s %s && ip link set %s up" % \
                       (tapname, self.bridge, tapname, tapname)
                run(_cmd, shell=True, check=True)
            except CalledProcessError:
                _cmd = "tunctl -t %s && brctl addif %s %s && ip link set %s up" % \
                       (tapname, self.bridge, tapname, tapname)
                run(_cmd, shell=True, check=True)
                NetworkResource.tap_cmd = "tunctl"

        mac = generate_random_mac()
        return {"name": tapname, "mac": mac}

    def add_to_bridge(self, tapname):
        """Add tap device to bridge"""
        _cmd = "ip link show %s && brctl addif %s %s && ip link set %s up" % \
               (tapname, self.bridge, tapname, tapname)
        run(_cmd, shell=True, check=True)

    def clean_tap(self, tapname):
        """Clean tap device from host"""
        if NetworkResource.tap_cmd == "tunctl":
            _cmd = "ip link set %s down 2>/dev/null; brctl delif %s %s 2>/dev/null;" \
                   "tunctl -d %s 2>/dev/null" % (tapname, self.bridge, tapname, tapname)
        else:
            _cmd = "ip link set %s down 2>/dev/null; brctl delif %s %s 2>/dev/null;" \
                   "ip tuntap del %s mode tap 2>/dev/null" % \
                   (tapname, self.bridge, tapname, tapname)
        run(_cmd, shell=True, check=False)
        with self.lock:
            if tapname in self.ip_resources:
                static_index = int(str(self.ip_resources[tapname]["ipaddr"]).split(".")[-1])
                self.static_ip_range.append(static_index)
                del self.ip_resources[tapname]

    def alloc_ipaddr(self, tapname, index=0):
        """
        Alloc an static ip address

        Returns:
            {"ipaddr": xxx, "netmask": xxx, "netmasklen": xxx, "gateway": xxx}
        """
        with self.lock:
            if tapname in self.ip_resources:
                return self.ip_resources[tapname]

            if not self.static_ip_range:
                return None
            static_index = random.choice(self.static_ip_range)
            self.static_ip_range.remove(static_index)
            _temp = {"ipaddr": "%s.%s.%s" % (self.ip_prefix, str(self.ip_3rd + index), str(static_index)),
                     "netmask": self.netmask,
                     "netmasklen": self.netmasklen,
                     "gateway": self.ipaddr}
            self.ip_resources[tapname] = _temp
            return self.ip_resources[tapname]


class VsockResource(Singleton):
    """Vsock resource"""
    def __init__(self):
        self.lock = threading.Lock()
        self.used_cids = set()

    @staticmethod
    def init_vsock():
        """Init vsock"""
        if run("lsmod | grep vhost_vsock", shell=True, check=False).returncode != 0:
            if run("modprobe vhost_vsock", shell=True, check=False).returncode != 0:
                return False

        return True

    @staticmethod
    def find_contextid():
        """Find uniq context ID"""
        first_cid = 3
        max_cid = 10000
        rand_cid = random.choice(range(first_cid, max_cid))
        return rand_cid


NETWORKS = NetworkResource()
VSOCKS = VsockResource()