# -- coding: utf-8 --
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you 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 CsHelper
import logging


class CsRoute:

    """ Manage routes """

    def __init__(self):
        self.table_prefix = "Table_"

    def get_tablename(self, name):
        return self.table_prefix + name

    def add_table(self, devicename):
        tablenumber = 100 + int(devicename[3:])
        tablename = self.get_tablename(devicename)
        str = "%s %s" % (tablenumber, tablename)
        filename = "/etc/iproute2/rt_tables"
        logging.info("Adding route table: " + str + " to " + filename + " if not present ")
        if not CsHelper.definedinfile(filename, str):
            CsHelper.execute("sudo echo " + str + " >> /etc/iproute2/rt_tables")
        # remove "from all table tablename" if exists, else it will interfer with
        # routing of unintended traffic
        if self.findRule("from all lookup " + tablename):
            CsHelper.execute("sudo ip rule delete from all table " + tablename)

    def flush_table(self, tablename):
        CsHelper.execute("ip route flush table %s" % (tablename))
        CsHelper.execute("ip route flush cache")

    def add_route(self, dev, address):
        """ Wrapper method that adds table name and device to route statement """
        # ip route add dev eth1 table Table_eth1 10.0.2.0/24
        table = self.get_tablename(dev)

        if not table or not address:
            empty_param = "table" if not table else "address"
            logging.info("Empty parameter received %s while trying to add route, skipping" % empty_param)
        else:
            logging.info("Adding route: dev " + dev + " table: " +
                         table + " network: " + address + " if not present")
            cmd = "default via %s table %s proto static" % (address, table)
            self.set_route(cmd)

    def add_network_route(self, dev, address):
        """ Wrapper method that adds table name and device to route statement """
        # ip route add dev eth1 table Table_eth1 10.0.2.0/24
        table = self.get_tablename(dev)

        if not table or not address:
            empty_param = "table" if not table else "address"
            logging.info("Empty parameter received %s while trying to add network route, skipping" % empty_param)
        else:
            logging.info("Adding route: dev " + dev + " table: " +
                         table + " network: " + address + " if not present")
            cmd = "throw %s table %s proto static" % (address, table)
            self.set_route(cmd)

    def set_route(self, cmd, method="add"):
        """ Add a route if it is not already defined """
        found = False
        search = cmd
        if "throw" in search:
            search = search.replace("throw", "")
        for i in CsHelper.execute("ip route show " + search):
            found = True
        if not found and method == "add":
            logging.info("Add " + cmd)
            cmd = "ip route add " + cmd
        elif found and method == "delete":
            logging.info("Delete " + cmd)
            cmd = "ip route delete " + cmd
        else:
            return
        CsHelper.execute(cmd)

    def add_defaultroute(self, gateway):
        """  Add a default route
        :param str gateway
        :return: bool
        """
        if not gateway:
            raise Exception("Gateway cannot be None.")

        if self.defaultroute_exists():
            return False
        else:
            cmd = "default via " + gateway
            logging.info("Adding default route")
            self.set_route(cmd)
            return True

    def defaultroute_exists(self):
        """ Return True if a default route is present
        :return: bool
        """
        logging.info("Checking if default ipv4 route is present")
        route_found = CsHelper.execute("ip -4 route list 0/0")

        if len(route_found) > 0:
            logging.info("Default route found: " + route_found[0])
            return True
        else:
            logging.warn("No default route found!")
            return False

    def findRule(self, rule):
        for i in CsHelper.execute("ip rule show"):
            if rule in i.strip():
                return True
        return False

    def set_route_v6(self, cmd, method="add"):
        """ Add a IPv6 route if it is not already defined """
        found = False
        search = cmd
        for i in CsHelper.execute("ip -6 route show " + search):
            found = True
        if not found and method == "add":
            logging.info("Add " + cmd)
            cmd = "ip -6 route add " + cmd
        elif found and method == "delete":
            logging.info("Delete " + cmd)
            cmd = "ip -6 route delete " + cmd
        else:
            return
        CsHelper.execute(cmd)

    def add_defaultroute_v6(self, gateway):
        """  Add a default route
        # for example, ip -6 route add default via fd80:20:20:20::1
        :param str gateway
        :return: bool
        """
        if not gateway:
            raise Exception("Gateway cannot be None.")

        logging.info("Checking if default ipv6 route is present")
        route_found = CsHelper.execute("ip -6 route list default")
        if len(route_found) > 0:
            logging.info("Default IPv6 route found: " + route_found[0])
            return False
        else:
            cmd = "default via " + gateway
            logging.info("Adding default IPv6 route")
            self.set_route_v6(cmd)
            return True

    def add_network_route_v6(self, dev, address):
        """ Wrapper method that adds table name and device to route statement """
        # ip -6 route add dev eth1 2021:10:10:10::1/64
        logging.info("Adding IPv6  route: dev " + dev + " network: " + address + " if not present")
        cmd = "%s dev %s proto kernel" % (address, dev)
        self.set_route_v6(cmd)

    def delete_network_route_v6(self, dev, address):
        """ Wrapper method that deletes table name and device from route statement """
        # ip -6 route del dev eth1 2021:10:10:10::1/64
        logging.info("Deleting IPv6 route: dev " + dev + " network: " + address + " if present")
        cmd = "%s dev %s" % (address, dev)
        self.set_route_v6(cmd, method="delete")
