# -*- Coding: utf-8 -*-

# Copyright 2014-2015 Red Hat, Inc.
#
# 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.

import logging

import netaddr
import os_net_config
from os_net_config import objects
from os_net_config import utils


logger = logging.getLogger(__name__)


# TODO(?): should move to interfaces.d
def _network_config_path(prefix=''):
    return prefix + "/etc/network/interfaces"


class ENINetConfig(os_net_config.NetConfig):
    """Debian/Ubuntu implementation for network config

       Configure iface/bridge/routes using debian/ubuntu
       /etc/network/interfaces format.
    """

    def __init__(self, noop=False, root_dir=''):
        super(ENINetConfig, self).__init__(noop, root_dir)
        self.interfaces = {}
        self.routes = {}
        self.bridges = {}
        logger.info('ENI net config provider created.')

    def _add_common(self, interface, static_addr=None, ip_version=4):

        ovs_extra = []
        data = ""
        address_data = ""
        if static_addr:
            address_data += "    address %s\n" % static_addr.ip
            if ip_version == 6:
                address_data += "    netmask %s\n" % static_addr.prefixlen
            else:
                address_data += "    netmask %s\n" % static_addr.netmask
        else:
            v4_addresses = interface.v4_addresses()
            if v4_addresses:
                for v4_address in v4_addresses:
                    data += self._add_common(interface, v4_address)

            v6_addresses = interface.v6_addresses()
            if v6_addresses:
                for v6_address in v6_addresses:
                    data += self._add_common(interface, v6_address, 6)

            if data:
                return data

        if isinstance(interface, objects.Vlan):
            _iface = "iface vlan%i " % interface.vlan_id
        else:
            _iface = "iface %s " % interface.name
        if static_addr and static_addr.version == 6:
            _iface += "inet6 "
        else:
            _iface += "inet "
        if interface.use_dhcp:
            _iface += "dhcp\n"
        elif interface.addresses:
            _iface += "static\n"
        else:
            _iface += "manual\n"
        if isinstance(interface, objects.OvsBridge):
            data += "auto %s\n" % interface.name
            data += "allow-ovs %s\n" % interface.name
            data += _iface
            data += address_data
            data += "    ovs_type OVSBridge\n"
            if interface.members:
                data += "    ovs_ports"
                for i in interface.members:
                    data += " %s" % i.name
                data += "\n"
                for mem in interface.members:
                    if isinstance(mem, objects.Interface):
                        data += "    pre-up ip addr flush dev %s\n" % mem.name
                if interface.primary_interface_name:
                    mac = utils.interface_mac(interface.primary_interface_name)
                    ovs_extra.append("set bridge %s other-config:hwaddr=%s" %
                                     (interface.name, mac))
            ovs_extra.extend(interface.ovs_extra)
        elif interface.ovs_port:
            if isinstance(interface, objects.Vlan):
                data += "auto vlan%i\n" % interface.vlan_id
                data += "allow-%s vlan%i\n" % (interface.bridge_name,
                                               interface.vlan_id)
                data += _iface
                data += address_data
                data += "    ovs_bridge %s\n" % interface.bridge_name
                data += "    ovs_type OVSIntPort\n"
                data += "    ovs_options tag=%s\n" % interface.vlan_id

            else:
                data += "auto %s\n" % interface.name
                data += "allow-%s %s\n" % (interface.bridge_name,
                                           interface.name)
                data += _iface
                data += address_data
                data += "    ovs_bridge %s\n" % interface.bridge_name
                data += "    ovs_type OVSPort\n"
        elif isinstance(interface, objects.Vlan):
            data += "auto vlan%i\n" % interface.vlan_id
            data += _iface
            data += address_data
            data += "    vlan-raw-device %s\n" % interface.device
        else:
            if isinstance(interface, objects.Interface) and interface.hotplug:
                data += "allow-hotplug %s\n" % interface.name
            else:
                data += "auto %s\n" % interface.name
            data += _iface
            data += address_data
        if interface.mtu:
            data += "    mtu %i\n" % interface.mtu

        if interface.hwaddr:
            raise NotImplementedError("hwaddr is not implemented.")

        if ovs_extra:
            data += "    ovs_extra %s\n" % " -- ".join(ovs_extra)

        return data

    def add_interface(self, interface):
        """Add an Interface object to the net config object.

        :param interface: The Interface object to add.
        """
        logger.info('adding interface: %s' % interface.name)
        data = self._add_common(interface)
        logger.debug('interface data: %s' % data)
        self.interfaces[interface.name] = data
        if interface.routes:
            self._add_routes(interface.name, interface.routes)

    def add_bridge(self, bridge):
        """Add an OvsBridge object to the net config object.

        :param bridge: The OvsBridge object to add.
        """
        logger.info('adding bridge: %s' % bridge.name)
        data = self._add_common(bridge)
        logger.debug('bridge data: %s' % data)
        self.bridges[bridge.name] = data
        if bridge.routes:
            self._add_routes(bridge.name, bridge.routes)

    def add_vlan(self, vlan):
        """Add a Vlan object to the net config object.

        :param vlan: The vlan object to add.
        """
        logger.info('adding vlan: %s' % vlan.name)
        data = self._add_common(vlan)
        logger.debug('vlan data: %s' % data)
        self.interfaces[vlan.name] = data
        if vlan.routes:
            self._add_routes(vlan.name, vlan.routes)

    def _add_routes(self, interface_name, routes=[]):
        logger.info('adding custom route for interface: %s' % interface_name)
        data = ""
        for route in routes:
            options = ""
            if route.route_options:
                options = " %s" % (route.route_options)
            if route.default and not route.ip_netmask:
                rt = netaddr.IPNetwork("0.0.0.0/0")
            else:
                rt = netaddr.IPNetwork(route.ip_netmask)
            data += "up route add -net %s netmask %s gw %s%s\n" % (
                    str(rt.ip), str(rt.netmask), route.next_hop, options)
            data += "down route del -net %s netmask %s gw %s%s\n" % (
                    str(rt.ip), str(rt.netmask), route.next_hop, options)
        self.routes[interface_name] = data
        logger.debug('route data: %s' % self.routes[interface_name])

    def apply(self, cleanup=False, activate=True):
        """Apply the network configuration.

        :param cleanup: A boolean which indicates whether any undefined
            (existing but not present in the object model) interface
            should be disabled and deleted.
        :param activate: A boolean which indicates if the config should
            be activated by stopping/starting interfaces
        :returns: a dict of the format: filename/data which contains info
            for each file that was changed (or would be changed if in --noop
            mode).
        Note the noop mode is set via the constructor noop boolean
        """
        new_config = ""

        # write out bridges first. This ensures that an ifup -a
        # on reboot brings them up first
        for bridge_name, bridge_data in self.bridges.items():
            route_data = self.routes.get(bridge_name)
            bridge_data += (route_data or '')
            new_config += bridge_data

        for interface_name, iface_data in self.interfaces.items():
            route_data = self.routes.get(interface_name)
            iface_data += (route_data or '')
            new_config += iface_data

        if utils.diff(_network_config_path(self.root_dir), new_config):
            if activate:
                for interface in self.interfaces.keys():
                    self.ifdown(interface)

                for bridge in self.bridges.keys():
                    self.ifdown(bridge, iftype='bridge')

            self.write_config(_network_config_path(self.root_dir), new_config)

            if activate:
                for bridge in self.bridges.keys():
                    self.ifup(bridge, iftype='bridge')

                for interface in self.interfaces.keys():
                    self.ifup(interface)

                if self.errors:
                    message = 'Failure(s) occurred when applying configuration'
                    logger.error(message)
                    for e in self.errors:
                        logger.error('stdout: %s, stderr: %s', e.stdout,
                                     e.stderr)
                    raise os_net_config.ConfigurationError(message)
        else:
            logger.info('No interface changes are required.')

        return {_network_config_path(self.root_dir): new_config}
