import os
import socket

from lxml import etree

from core import constants
from core import logger
from core.coreobj import PyCoreNode
from core.enumerations import NodeTypes
from core.misc import utils, nodeutils, ipaddress


def add_type(parent_element, name):
    type_element = etree.SubElement(parent_element, "type")
    type_element.text = name


def add_address(parent_element, address_type, address, interface_name=None):
    address_element = etree.SubElement(parent_element, "address", type=address_type)
    address_element.text = address
    if interface_name is not None:
        address_element.set("iface", interface_name)


def add_mapping(parent_element, maptype, mapref):
    etree.SubElement(parent_element, "mapping", type=maptype, ref=mapref)


def add_emane_interface(host_element, netif, platform_name="p1", transport_name="t1"):
    nem_id = netif.net.nemidmap[netif]
    host_id = host_element.get("id")

    # platform data
    platform_id = "%s/%s" % (host_id, platform_name)
    platform_element = etree.SubElement(host_element, "emanePlatform", id=platform_id, name=platform_name)

    # transport data
    transport_id = "%s/%s" % (host_id, transport_name)
    etree.SubElement(platform_element, "transport", id=transport_id, name=transport_name)

    # nem data
    nem_name = "nem%s" % nem_id
    nem_element_id = "%s/%s" % (host_id, nem_name)
    nem_element = etree.SubElement(platform_element, "nem", id=nem_element_id, name=nem_name)
    nem_id_element = etree.SubElement(nem_element, "parameter", name="nemid")
    nem_id_element.text = str(nem_id)

    return platform_element


def get_address_type(address):
    addr, slash, prefixlen = address.partition("/")
    if ipaddress.is_ipv4_address(addr):
        address_type = "IPv4"
    elif ipaddress.is_ipv6_address(addr):
        address_type = "IPv6"
    else:
        raise NotImplementedError
    return address_type


def get_ipv4_addresses(hostname):
    if hostname == "localhost":
        addresses = []
        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]
            address = split[3]
            if not address.startswith("127."):
                addresses.append((interface_name, address))
        return addresses
    else:
        # TODO: handle other hosts
        raise NotImplementedError


class CoreXmlDeployment(object):
    def __init__(self, session, scenario):
        self.session = session
        self.scenario = scenario
        self.root = etree.SubElement(scenario, "container", id="TestBed", name="TestBed")
        self.add_deployment()

    def find_device(self, name):
        device = self.scenario.find("devices/device[@name='%s']" % name)
        logger.info("specific found scenario device: %s", device)
        return device

    def find_interface(self, device, name):
        interface = self.scenario.find("devices/device[@name='%s']/interfaces/interface[@name='%s']" % (
            device.name, name))
        logger.info("specific found scenario interface: %s", interface)
        return interface

    def add_deployment(self):
        physical_host = self.add_physical_host(socket.gethostname())

        # TODO: handle other servers
        #   servers = self.session.broker.getservernames()
        #   servers.remove("localhost")

        for node in self.session.objects.itervalues():
            if isinstance(node, PyCoreNode):
                self.add_virtual_host(physical_host, node)

    def add_physical_host(self, name):
        # add host
        host_id = "%s/%s" % (self.root.get("id"), name)
        host_element = etree.SubElement(self.root, "testHost", id=host_id, name=name)

        # add type element
        add_type(host_element, "physical")

        # add ipv4 addresses
        for interface_name, address in get_ipv4_addresses("localhost"):
            add_address(host_element, "IPv4", address, interface_name)

        return host_element

    def add_virtual_host(self, physical_host, node):
        assert isinstance(node, PyCoreNode)

        # create virtual host element
        host_id = "%s/%s" % (physical_host.get("id"), node.name)
        host_element = etree.SubElement(physical_host, "testHost", id=host_id, name=node.name)

        # TODO: need to inject mapping into device element?
        self.find_device(node.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
        # add_mapping(device, "testHost", host_id)

        # add host type
        add_type(host_element, "virtual")

        for netif in node.netifs():
            emane_element = None
            if nodeutils.is_node(netif.net, NodeTypes.EMANE):
                emane_element = add_emane_interface(host_element, netif)

            parent_element = host_element
            if emane_element is not None:
                parent_element = emane_element

            for address in netif.addrlist:
                address_type = get_address_type(address)
                add_address(parent_element, address_type, address, netif.name)

                # TODO: need to inject mapping in interface?
                # interface = self.find_interface(device, netif.name)
                # add_mapping(interface, "nem", nem.getAttribute("id"))