import os
import socket

from core import constants
from core import logger
from core.enumerations import NodeTypes
from core.misc import ipaddress
from core.misc import nodeutils
from core.misc import utils
from core.netns import nodes
from core.xml import xmlutils


class CoreDeploymentWriter(object):
    def __init__(self, dom, root, session):
        self.dom = dom
        self.root = root
        self.session = session
        self.hostname = socket.gethostname()

    @staticmethod
    def get_ipv4_addresses(hostname):
        if hostname == 'localhost':
            addr_list = []
            args = [constants.IP_BIN, '-o', '-f', 'inet', 'addr', 'show']
            output = utils.check_cmd(args)
            for line in output.split(os.linesep):
                split = line.split()
                if not split:
                    continue
                addr = split[3]
                if not addr.startswith('127.'):
                    addr_list.append(addr)
            return addr_list
        else:
            # TODO: handle other hosts
            raise NotImplementedError

    @staticmethod
    def get_interface_names(hostname):
        """
        Uses same methodology of get_ipv4_addresses() to get
        parallel list of interface names to go with ...
        """
        if hostname == 'localhost':
            iface_list = []
            args = [constants.IP_BIN, '-o', '-f', 'inet', 'addr', 'show']
            output = utils.check_cmd(args)
            for line in output.split(os.linesep):
                split = line.split()
                if not split:
                    continue
                interface_name = split[1]
                addr = split[3]
                if not addr.startswith('127.'):
                    iface_list.append(interface_name)
            return iface_list
        else:
            # TODO: handle other hosts
            raise NotImplementedError

    @staticmethod
    def find_device(scenario, name):
        tag_name = ('device', 'host', 'router')
        for d in xmlutils.iter_descendants_with_attribute(scenario, tag_name, 'name', name):
            return d
        return None

    @staticmethod
    def find_interface(device, name):
        for i in xmlutils.iter_descendants_with_attribute(device, 'interface', 'name', name):
            return i
        return None

    def add_deployment(self):
        testbed = self.dom.createElement('container')
        testbed.setAttribute('name', 'TestBed')
        testbed.setAttribute('id', 'TestBed')
        self.root.base_element.appendChild(testbed)
        nodelist = []
        for obj in self.session.objects.itervalues():
            if isinstance(obj, nodes.PyCoreNode):
                nodelist.append(obj)
        name = self.hostname
        ipv4_addresses = self.get_ipv4_addresses('localhost')
        iface_names = self.get_interface_names('localhost')
        testhost = self.add_physical_host(testbed, name, ipv4_addresses, iface_names)
        for n in nodelist:
            self.add_virtual_host(testhost, n)
            # TODO: handle other servers
            #   servers = self.session.broker.getservernames()
            #   servers.remove('localhost')

    def add_child_element(self, parent, tag_name):
        el = self.dom.createElement(tag_name)
        parent.appendChild(el)
        return el

    def add_child_element_with_nameattr(self, parent, tag_name, name, setid=True):
        el = self.add_child_element(parent, tag_name)
        el.setAttribute('name', name)
        if setid:
            el.setAttribute('id', '%s/%s' % (parent.getAttribute('id'), name))
        return el

    def add_address(self, parent, address_type, address_str, address_iface=None):
        el = self.add_child_element(parent, 'address')
        el.setAttribute('type', address_type)
        if address_iface is not None:
            el.setAttribute('iface', address_iface)
        el.appendChild(self.dom.createTextNode(address_str))
        return el

    def add_type(self, parent, type_str):
        el = self.add_child_element(parent, 'type')
        el.appendChild(self.dom.createTextNode(type_str))
        return el

    def add_platform(self, parent, name):
        el = self.add_child_element_with_nameattr(parent, 'emanePlatform', name)
        return el

    def add_transport(self, parent, name):
        el = self.add_child_element_with_nameattr(parent, 'transport', name)
        return el

    def add_nem(self, parent, name):
        el = self.add_child_element_with_nameattr(parent, 'nem', name)
        return el

    def add_parameter(self, parent, name, val):
        el = self.add_child_element_with_nameattr(parent, 'parameter', name, False)
        el.appendChild(self.dom.createTextNode(val))
        return el

    def add_mapping(self, parent, maptype, mapref):
        el = self.add_child_element(parent, 'mapping')
        el.setAttribute('type', maptype)
        el.setAttribute('ref', mapref)
        return el

    def add_host(self, parent, name):
        el = self.add_child_element_with_nameattr(parent, 'testHost', name)
        return el

    def add_physical_host(self, parent, name, ipv4_addresses, iface_names):
        el = self.add_host(parent, name)
        self.add_type(el, 'physical')
        for i in range(0, len(ipv4_addresses)):
            addr = ipv4_addresses[i]
            if iface_names:
                interface_name = iface_names[i]
            else:
                interface_name = None
            self.add_address(el, 'IPv4', addr, interface_name)
        return el

    def add_virtual_host(self, parent, obj):
        assert isinstance(obj, nodes.PyCoreNode)
        el = self.add_host(parent, obj.name)
        device = self.find_device(self.root.base_element, obj.name)
        if device is None:
            logger.warn('corresponding XML device not found for %s' % obj.name)
            return
        self.add_mapping(device, 'testHost', el.getAttribute('id'))
        self.add_type(el, 'virtual')
        for netif in obj.netifs():
            for address in netif.addrlist:
                addr, slash, prefixlen = address.partition('/')
                if ipaddress.is_ipv4_address(addr):
                    addr_type = 'IPv4'
                elif ipaddress.is_ipv6_address(addr):
                    addr_type = 'IPv6'
                else:
                    raise NotImplementedError
                self.add_address(el, addr_type, address, netif.name)
            if nodeutils.is_node(netif.net, NodeTypes.EMANE):
                nem = self.add_emane_interface(parent, el, netif)
                interface = self.find_interface(device, netif.name)
                self.add_mapping(interface, 'nem', nem.getAttribute('id'))
        return el

    def add_emane_interface(self, physical_host, virtual_host, netif, platform_name='p1', transport_name='t1'):
        nemid = netif.net.nemidmap[netif]
        platform = self.add_platform(virtual_host, name=platform_name)
        transport = self.add_transport(virtual_host, name=transport_name)
        nem_name = 'nem%s' % nemid
        nem = self.add_nem(platform, nem_name)
        self.add_parameter(nem, 'nemid', str(nemid))
        self.add_mapping(transport, 'nem', nem.getAttribute('id'))
        return nem
