"""
commeffect.py: EMANE CommEffect model for CORE
"""

import logging
import os
from typing import Dict, List

from lxml import etree

from core.config import ConfigGroup, Configuration
from core.emane import emanemanifest, emanemodel
from core.nodes.interface import CoreInterface
from core.xml import emanexml

try:
    from emane.events.commeffectevent import CommEffectEvent
except ImportError:
    try:
        from emanesh.events.commeffectevent import CommEffectEvent
    except ImportError:
        logging.debug("compatible emane python bindings not installed")


def convert_none(x: float) -> int:
    """
    Helper to use 0 for None values.
    """
    if isinstance(x, str):
        x = float(x)
    if x is None:
        return 0
    else:
        return int(x)


class EmaneCommEffectModel(emanemodel.EmaneModel):
    name = "emane_commeffect"

    shim_library = "commeffectshim"
    shim_xml = "commeffectshim.xml"
    shim_defaults = {}
    config_shim = []

    # comm effect does not need the default phy and external configurations
    phy_config = []
    external_config = []

    @classmethod
    def load(cls, emane_prefix: str) -> None:
        shim_xml_path = os.path.join(emane_prefix, "share/emane/manifest", cls.shim_xml)
        cls.config_shim = emanemanifest.parse(shim_xml_path, cls.shim_defaults)

    @classmethod
    def configurations(cls) -> List[Configuration]:
        return cls.config_shim

    @classmethod
    def config_groups(cls) -> List[ConfigGroup]:
        return [ConfigGroup("CommEffect SHIM Parameters", 1, len(cls.configurations()))]

    def build_xml_files(
        self, config: Dict[str, str], interface: CoreInterface = None
    ) -> None:
        """
        Build the necessary nem and commeffect XMLs in the given path.
        If an individual NEM has a nonstandard config, we need to build
        that file also. Otherwise the WLAN-wide
        nXXemane_commeffectnem.xml, nXXemane_commeffectshim.xml are used.

        :param config: emane model configuration for the node and interface
        :param interface: interface for the emane node
        :return: nothing
        """
        # retrieve xml names
        nem_name = emanexml.nem_file_name(self, interface)
        shim_name = emanexml.shim_file_name(self, interface)

        # create and write nem document
        nem_element = etree.Element("nem", name=f"{self.name} NEM", type="unstructured")
        transport_type = "virtual"
        if interface and interface.transport_type == "raw":
            transport_type = "raw"
        transport_file = emanexml.transport_file_name(self.id, transport_type)
        etree.SubElement(nem_element, "transport", definition=transport_file)

        # set shim configuration
        etree.SubElement(nem_element, "shim", definition=shim_name)

        nem_file = os.path.join(self.session.session_dir, nem_name)
        emanexml.create_file(nem_element, "nem", nem_file)

        # create and write shim document
        shim_element = etree.Element(
            "shim", name=f"{self.name} SHIM", library=self.shim_library
        )

        # append all shim options (except filterfile) to shimdoc
        for configuration in self.config_shim:
            name = configuration.id
            if name == "filterfile":
                continue
            value = config[name]
            emanexml.add_param(shim_element, name, value)

        # empty filterfile is not allowed
        ff = config["filterfile"]
        if ff.strip() != "":
            emanexml.add_param(shim_element, "filterfile", ff)

        shim_file = os.path.join(self.session.session_dir, shim_name)
        emanexml.create_file(shim_element, "shim", shim_file)

    def linkconfig(
        self,
        netif: CoreInterface,
        bw: float = None,
        delay: float = None,
        loss: float = None,
        duplicate: float = None,
        jitter: float = None,
        netif2: CoreInterface = None,
    ) -> None:
        """
        Generate CommEffect events when a Link Message is received having
        link parameters.
        """
        service = self.session.emane.service
        if service is None:
            logging.warning("%s: EMANE event service unavailable", self.name)
            return

        if netif is None or netif2 is None:
            logging.warning("%s: missing NEM information", self.name)
            return

        # TODO: batch these into multiple events per transmission
        # TODO: may want to split out seconds portion of delay and jitter
        event = CommEffectEvent()
        emane_node = self.session.get_node(self.id)
        nemid = emane_node.getnemid(netif)
        nemid2 = emane_node.getnemid(netif2)
        mbw = bw
        logging.info("sending comm effect event")
        event.append(
            nemid,
            latency=convert_none(delay),
            jitter=convert_none(jitter),
            loss=convert_none(loss),
            duplicate=convert_none(duplicate),
            unicast=int(convert_none(bw)),
            broadcast=int(convert_none(mbw)),
        )
        service.publish(nemid2, event)
