package be.iminds.ilabt.jfed.highlevel.stitcher;

import be.iminds.ilabt.jfed.call_log_output.HtmlLogOutput;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.*;
import be.iminds.ilabt.jfed.lowlevel.connection.MockSfaConnection;
import be.iminds.ilabt.jfed.lowlevel.stitching.VlanRange;
import be.iminds.ilabt.jfed.rspec.model.*;
import be.iminds.ilabt.jfed.rspec.parser.EventListExtraXml;
import be.iminds.ilabt.jfed.rspec.parser.ExtraXml;
import be.iminds.ilabt.jfed.rspec.parser.RspecParseException;
import be.iminds.ilabt.jfed.rspec.parser.RspecXmlConstants;
import be.iminds.ilabt.jfed.rspec.rspec_source.ManifestRspecSource;
import be.iminds.ilabt.jfed.rspec.rspec_source.RequestRspecSource;
import be.iminds.ilabt.jfed.util.GeniUrn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static be.iminds.ilabt.jfed.rspec.parser.RspecXmlConstants.*;

/**
 * TestStitchingAMv2
 */
public abstract class TestStitchingAMServer extends MockSfaConnection.AbstractMockXmlRpcReply {
    private final static Logger LOG = LoggerFactory.getLogger(TestStitchingAMServer.class);

    private static int globalIndex = 0;

    protected final String serverUrl;

    protected final String cmUrnString;
    protected final GeniUrn cmUrn;
    protected final String cmHostname;

    protected final String stitchIface;
    protected final String stitchIfaceUrn;

    protected final VlanRange availableVlans;
    protected final boolean supportsAny;
    protected final StitchingTestSetupConfig.StitchingTestSetupAmConfig amConfig;

    protected String activeVlan;

    protected final String advertisement;

    private final Map<String, Integer> callCounts = new HashMap<>();
    protected Server authority;

    protected final TestStitchingAMServer dependency;

    /**
     *
     * @param serverUrl
     * @param cmUrnString
     * @param cmHostname
     * @param stitchIface
     * @param availableVlans
     * @param amConfig
     * @param dependency this server depends on this server. May be null.
     */
    public TestStitchingAMServer(String serverUrl, String cmUrnString, String cmHostname, String stitchIface,
                                 VlanRange availableVlans,
                                 StitchingTestSetupConfig.StitchingTestSetupAmConfig amConfig,
                                 @Nullable TestStitchingAMServer dependency) {
        this.serverUrl = serverUrl;
        this.availableVlans = availableVlans;
        this.supportsAny = amConfig.supportsAny;
        this.amConfig = amConfig;
        this.cmHostname = cmHostname;
        this.cmUrnString = cmUrnString;
        this.dependency = dependency;
        try {
            this.cmUrn = new GeniUrn(cmUrnString);
        } catch (GeniUrn.GeniUrnParseException e) {
            throw new RuntimeException(e);
        }
        this.stitchIface = stitchIface;
        String stitchIfaceNode = stitchIface+"node"; //not really typical, but doesn't matter. if stitchIface is "c300b:0.12" this is normally "c300b"
        this.stitchIfaceUrn = "urn:publicid:IDN+"+cmHostname+"+interface+"+stitchIface;

        this.advertisement = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<rspec xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://www.geni.net/resources/rspec/3\" xmlns:emulab=\"http://www.protogeni.net/resources/rspec/ext/emulab/1\" xsi:schemaLocation=\"http://www.geni.net/resources/rspec/3 http://www.geni.net/resources/rspec/3/ad.xsd http://www.protogeni.net/resources/rspec/ext/emulab/1 http://www.protogeni.net/resources/rspec/ext/emulab/1/ptop_extension.xsd http://hpn.east.isi.edu/rspec/ext/stitch/0.1/ http://hpn.east.isi.edu/rspec/ext/stitch/0.1/stitch-schema.xsd http://www.geni.net/resources/rspec/ext/shared-vlan/1 http://www.geni.net/resources/rspec/ext/shared-vlan/1/ad.xsd http://www.geni.net/resources/rspec/ext/opstate/1 http://www.geni.net/resources/rspec/ext/opstate/1/ad.xsd \" type=\"advertisement\" generated=\"2015-03-03T15:44:25Z\" expires=\"2015-03-03T15:44:25Z\">\n" +
                "  <!-- This is a dummy advertisement, with just enough info for testing -->\n" +
                "\n" +
                "  <stitching xmlns=\"http://hpn.east.isi.edu/rspec/ext/stitch/0.1/\" lastUpdateTime=\"todo\">\n" +
                "    <aggregate id=\""+cmUrnString+"\" url=\""+serverUrl+"\">\n" +
                "      <aggregatetype>protogeni</aggregatetype>\n" +
                "      <stitchingmode>chainANDTree</stitchingmode>\n" +
                "      <scheduledservices>false</scheduledservices>\n" +
                "      <negotiatedservices>false</negotiatedservices>\n" +
                "      <lifetime id=\"life\">\n" +
                "        <start type=\"time\">todo</start>\n" +
                "        <end type=\"time\">todo</end>\n" +
                "      </lifetime>\n" +
                "      <node id=\"urn:publicid:IDN+"+cmHostname+"+node+"+stitchIfaceNode+"\">\n" +
                "        <port id=\"urn:publicid:IDN+"+cmHostname+"+stitchport+"+stitchIface+"\">\n" +
                "          <capacity>1000000</capacity>\n" +
                "          <maximumReservableCapacity>1000000</maximumReservableCapacity>\n" +
                "          <minimumReservableCapacity>1000</minimumReservableCapacity>\n" +
                "          <granularity>1</granularity>\n" +
                "          <link id=\""+stitchIfaceUrn+"\">\n" +
                "            <remoteLinkId>todo</remoteLinkId>\n" +
                "            <trafficEngineeringMetric>10</trafficEngineeringMetric>\n" +
                "            <capacity>1000000</capacity>\n" +
                "            <maximumReservableCapacity>1000000</maximumReservableCapacity>\n" +
                "            <minimumReservableCapacity>1000</minimumReservableCapacity>\n" +
                "            <granularity>1</granularity>\n" +
                "            <switchingCapabilityDescriptor>\n" +
                "              <switchingcapType>l2sc</switchingcapType>\n" +
                "              <encodingType>ethernet</encodingType>\n" +
                "              <switchingCapabilitySpecificInfo>\n" +
                "                <switchingCapabilitySpecificInfo_L2sc>\n" +
                "                  <interfaceMTU>1500</interfaceMTU>\n" +
                "                  <vlanRangeAvailability>"+availableVlans+"</vlanRangeAvailability>\n" +
                "                  <vlanTranslation>false</vlanTranslation>\n" +
                "                </switchingCapabilitySpecificInfo_L2sc>\n" +
                "              </switchingCapabilitySpecificInfo>\n" +
                "            </switchingCapabilityDescriptor>\n" +
                "          </link>\n" +
                "        </port>\n" +
                "      </node>\n" +
                "    </aggregate>\n" +
                "  </stitching>\n" +
                "</rspec>\n";
    }

    private final Map<String, ComponentInfo> infoByClientId = new HashMap<>();

    public void setAuthority(Server authority) {
        this.authority = authority;
    }

    protected class ComponentInfo {
        public final String type; //node, link, interface
        public final String clientId;
        public final String compId;
        public final String sliverId;
        public final int index;

        public ComponentInfo(String type, String clientId) {
            this.type = type;
            this.clientId = clientId;
            this.index = globalIndex++;
            this.compId = "urn:publicid:IDN+"+cmHostname+"+"+type+"+"+index;
            this.sliverId = "urn:publicid:IDN+"+cmHostname+"+sliver+"+type+index;
        }
    }
    protected ComponentInfo lookup(String clientId, String type) {
        ComponentInfo res = infoByClientId.get(clientId);
        if (res == null) {
            res = new  ComponentInfo(type, clientId);
            infoByClientId.put(clientId, res);
        }
        return res;
    }
    protected List<ComponentInfo> lookupByType(String type) {
        List<ComponentInfo> res = new ArrayList<>();
        for (ComponentInfo ci : infoByClientId.values()) {
            if (ci.type.equals(type))
                res.add(ci);
        }
        return res;
    }

    public class NoVlansAvailableException extends Exception {
        private final Integer vlan;

        public NoVlansAvailableException() {
            this.vlan = null;
        }
        public NoVlansAvailableException(Integer vlan) {
            this.vlan = vlan;
        }

        /** if null, no vlans are available. otherwise, a specific vlan is not available. */
        public Integer getVlan() {
            return vlan;
        }
    }

    public String createManifest(String requestRspec) throws NoVlansAvailableException {
        //just a silly delay for logging output order
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
        }

        RequestRspecSource requestRspecSource = new RequestRspecSource(requestRspec, ModelRspecType.BASIC);
        ModelRspec modelRspec = requestRspecSource.getModelRspec();
        for (RspecNode node : modelRspec.getNodes()) {
            if (node.getComponentManagerId().equals(cmUrn)) {
                ComponentInfo nci = lookup(node.getClientId(), "node");
                LOG.debug("FakeServer "+cmHostname+" saw node "+node.getClientId());
                node.setComponentId(nci.compId);
                node.setSliverId(nci.sliverId);
                for (RspecInterface iface : node.getInterfaces()) {
                    ComponentInfo ici = lookup(iface.getClientId(), "interface");
                    LOG.debug("FakeServer "+cmHostname+" saw interface "+iface.getClientId());
                    iface.setComponentId(ici.compId);
                    iface.setSliverId(ici.sliverId);
                    iface.setMacAddress("0123456789a"+('b'+ici.index));
                }
                //optional TODO: login services
            }
        }
        for (RspecLink link : modelRspec.getLinks()) {
            if (link.getComponentManagerUrns().contains(cmUrn)) {
                ComponentInfo lci = lookup(link.getClientId(), "link");
                LOG.debug("FakeServer "+cmHostname+" saw link "+link.getClientId());
                link.setSliverId(lci.sliverId);
                //TODO optional vlantag
                for (RspecInterface iface : link.getInterfaces())
                    if (infoByClientId.containsKey(iface.getClientId())) {
                        ComponentInfo ici = lookup(iface.getClientId(), "interface");
                        iface.setComponentId(ici.compId);
                        iface.setSliverId(ici.sliverId);
                    }
            }
        }

        if (!amConfig.noStitching) {
            String origSuggestedVlan = getSuggestedVlan(modelRspec);
            String availableVlansInRequest = getAvailableVlans(modelRspec);

            VlanRange availableVlansInRequestRange = new VlanRange(availableVlansInRequest);
            if (availableVlansInRequestRange.isEmpty())
                throw new RuntimeException("TEST BUG DETECTED: Invalid request, origAvailableVlan=" + availableVlansInRequest);

            Integer origSuggestedVlanInt = null;
            try {
                origSuggestedVlanInt = Integer.parseInt(origSuggestedVlan);

                if (!availableVlansInRequestRange.contains(origSuggestedVlanInt))
                    throw new RuntimeException("TEST BUG DETECTED: Invalid request, origAvailableVlan=" + availableVlansInRequest+" does not contain origSuggestedVlan="+origSuggestedVlan);
            } catch (NumberFormatException e) {
                origSuggestedVlanInt = null;
                if (!origSuggestedVlan.trim().equalsIgnoreCase("any"))
                    throw new RuntimeException("TEST BUG DETECTED: Invalid request, origSuggestedVlan=" + origSuggestedVlan);
            }

            String newSuggestedVlan = origSuggestedVlan;
            if (origSuggestedVlan != null && origSuggestedVlan.trim().equalsIgnoreCase("any")) {
                //pick a vlan from the ones in available that are also in the available in the request
                VlanRange rangeToPickFrom = new VlanRange(availableVlans);
                rangeToPickFrom.retainAll(availableVlansInRequestRange);

                if (rangeToPickFrom.isEmpty()) {
                    //not invalid request, but answer should be permanent error: no vlans are available in the provided range
                    throw new NoVlansAvailableException();
                }

                newSuggestedVlan = rangeToPickFrom.getFirst() + "";
            } else {
                if (origSuggestedVlan == null) {
                    throw new RuntimeException("TEST BUG DETECTED: Invalid request, cannot create manifest. origSuggestedVlan=" + origSuggestedVlan);
                }
                if (origSuggestedVlanInt == null) {
                    throw new RuntimeException("TEST BUG DETECTED: Invalid request, cannot create manifest origSuggestedVlan=" + origSuggestedVlan);
                }
                if (!availableVlans.contains(origSuggestedVlanInt)) {
                    //not invalid request, but answer should be error: requested vlans is not available
                    throw new NoVlansAvailableException(origSuggestedVlanInt);
                }
            }
//        setAvailableVlan(modelRspec);

            assert newSuggestedVlan != null;
            try {
                Integer newSuggestedVlanInt = Integer.parseInt(newSuggestedVlan);

                if (!availableVlansInRequestRange.contains(newSuggestedVlanInt))
                    throw new RuntimeException("TEST BUG DETECTED: createManifest() internal bug: Invalid reply, origAvailableVlan=" + availableVlansInRequest+" does not contain newSuggestedVlanInt="+newSuggestedVlanInt);
                if (!availableVlans.contains(newSuggestedVlanInt))
                    throw new RuntimeException("TEST BUG DETECTED: createManifest() internal bug: Invalid reply, availableVlans=" + availableVlans+" does not contain newSuggestedVlanInt="+newSuggestedVlanInt);
            } catch (NumberFormatException e) {
                throw new RuntimeException("TEST BUG DETECTED: createManifest() internal bug: Invalid reply newSuggestedVlan is not an int but \""+newSuggestedVlan+"\"", e);
            }

            LOG.debug("creating manifest on " + cmUrnString + " for availableVlans=" + availableVlans + " newSuggestedVlan=" + newSuggestedVlan);
            rewriteVlans(modelRspec, newSuggestedVlan, newSuggestedVlan);
            activeVlan = newSuggestedVlan;
        }
        ManifestRspecSource manifestRspecSource = new ManifestRspecSource(modelRspec);
        String res = manifestRspecSource.getRspecXmlString();
        LOG.debug("Manifest stitching overview: "+ HtmlLogOutput.makeStitchingOverview(res));
        return res;
    }

    /**
     *
     * @param rspec
     * @return the original suggested Vlan
     * @throws XMLStreamException
     */
    protected String getSuggestedVlan(String rspec) {
        RequestRspecSource requestRspecSource = new RequestRspecSource(rspec, ModelRspecType.BASIC);
        ModelRspec modelRspec = requestRspecSource.getModelRspec();
        return getSuggestedVlan(modelRspec);
    }
    protected String getSuggestedVlan(ModelRspec modelRspec ) {
        try {
            List<ExtraXml> origExtraXmls = modelRspec.getExtraXml();
            LOG.debug("vlanHelper entry extraXml.size()="+origExtraXmls.size()+" @ "+cmUrnString);

            List<ExtraXml> newExtraXmls = new ArrayList<>();
            for (ExtraXml extraXml : origExtraXmls) {
                LOG.debug("vlanHelper extraXml \""+extraXml.getStartElementName()+"\" @ "+cmUrnString);
                if (RspecXmlConstants.Q_STITCHING.equals(extraXml.getStartElementName())) {
                    assert extraXml instanceof EventListExtraXml;

                    String curPathName = null;
                    String curLinkUrn = null;

                    XMLEventReader eventReader = ((EventListExtraXml)extraXml).getAsEventReader();
                    while (eventReader.hasNext()) {
                        XMLEvent e = eventReader.nextEvent();
                        if (e.isStartElement()) {
                            if (e.asStartElement().getName().equals(Q_STITCHING_PATH)) {
                                curPathName = e.asStartElement().getAttributeByName(Q_STITCHING_PATH_ID).getValue();
                            }
                            if (e.asStartElement().getName().equals(Q_STITCHING_LINK)) {
                                curLinkUrn = e.asStartElement().getAttributeByName(Q_STITCHING_LINK_ID).getValue();
                                LOG.debug("vlanHelper extraXml curLinkUrn=\""+curLinkUrn+"\"  <-> stitchIfaceUrn=\""+stitchIfaceUrn+"\" @ "+cmUrnString);
                            }
                            if (e.asStartElement().getName().equals(Q_STITCHING_VLAN_SUGGESTED) && curLinkUrn.equals(stitchIfaceUrn)) {
                                EventListExtraXml x = new EventListExtraXml(e.asStartElement(), eventReader);
                                XMLEventReader eventReader2 = x.getAsEventReader();
                                while (eventReader2.hasNext()) {
                                    XMLEvent e2 = eventReader2.nextEvent();
                                    if (e2.isCharacters()) {
                                        return e2.asCharacters().getData();
                                    }
                                }
                            }
                        }
                    }
                } else
                    newExtraXmls.add(extraXml);
            }
            return null;
        } catch (XMLStreamException e) {
            //            LOG.error(e);
            throw new RuntimeException(e);
        } catch (RspecParseException e) {
            //            LOG.error(e);
            throw new RuntimeException(e);
        }
    }
    protected String rewriteVlans(ModelRspec modelRspec, String newAvailable, String newSuggested)  {
        try {
            String origSuggested = null;

            List<ExtraXml> origExtraXmls = modelRspec.getExtraXml();
            LOG.debug("vlanHelper entry extraXml.size()="+origExtraXmls.size()+" @ "+cmUrnString);

            List<ExtraXml> newExtraXmls = new ArrayList<>();
            for (ExtraXml extraXml : origExtraXmls) {
                LOG.debug("vlanHelper extraXml \""+extraXml.getStartElementName()+"\" @ "+cmUrnString);
                if (RspecXmlConstants.Q_STITCHING.equals(extraXml.getStartElementName())) {
                    assert extraXml instanceof EventListExtraXml;
                    final List<XMLEvent> newEvents = new ArrayList<>();

                    String curPathName = null;
                    String curLinkUrn = null;

                    XMLEventFactory eventFactory = XMLEventFactory.newInstance();

                    XMLEventReader eventReader = ((EventListExtraXml)extraXml).getAsEventReader();
                    while (eventReader.hasNext()) {
                        XMLEvent e = eventReader.nextEvent();
                        if (e.isStartElement()) {
                            boolean handled = false;
                            if (e.asStartElement().getName().equals(Q_STITCHING_PATH)) {
                                curPathName = e.asStartElement().getAttributeByName(Q_STITCHING_PATH_ID).getValue();
                            }
                            if (e.asStartElement().getName().equals(Q_STITCHING_LINK)) {
                                curLinkUrn = e.asStartElement().getAttributeByName(Q_STITCHING_LINK_ID).getValue();
                                LOG.debug("vlanHelper extraXml curLinkUrn=\""+curLinkUrn+"\"  <-> stitchIfaceUrn=\""+stitchIfaceUrn+"\" @ "+cmUrnString);
                            }
                            if (e.asStartElement().getName().equals(Q_STITCHING_VLAN_AVAILABLE) && curLinkUrn.equals(stitchIfaceUrn)) {
                                LOG.debug("Rewrite available for manifest: entry");
                                if (newAvailable != null) {
                                    handled = true;

                                    EventListExtraXml x = new EventListExtraXml(e.asStartElement(), eventReader);
                                    newEvents.add(e);
                                    newEvents.add(eventFactory.createCharacters(newAvailable));
                                    newEvents.add(eventFactory.createEndElement(Q_STITCHING_VLAN_AVAILABLE, null/*namespaces*/));
                                    LOG.debug("Rewrite available for manifest: " + newAvailable);
                                }
                            }
                            if (e.asStartElement().getName().equals(Q_STITCHING_VLAN_SUGGESTED) && curLinkUrn.equals(stitchIfaceUrn)) {
                                LOG.debug("Rewrite suggested for manifest: entry");
                                handled = true;
                                EventListExtraXml x = new EventListExtraXml(e.asStartElement(), eventReader);
                                XMLEventReader eventReader2 = x.getAsEventReader();
                                while (eventReader2.hasNext()) {
                                    XMLEvent e2 = eventReader2.nextEvent();
                                    if (e2.isCharacters()) {
                                        origSuggested = e2.asCharacters().getData();

                                        if (newSuggested == null) {
                                            if (!supportsAny && origSuggested.trim().equalsIgnoreCase("any")) {
                                                newEvents.add(eventFactory.createCharacters(availableVlans.getFirst()+""));
                                                LOG.debug("Rewrite suggested for manifest: UNREQUESTED OVERWRITE supportsAny="+supportsAny+" origSuggested=" + origSuggested + " newSuggested=NULL availableVlans.getCurrent()=" + availableVlans.getFirst()+"  @ "+cmUrnString);
                                            } else {
                                                LOG.debug("Rewrite suggested for manifest: supportsAny="+supportsAny+" origSuggested=" + origSuggested+" newSuggested=NULL"+"  @ "+cmUrnString);
                                                newEvents.add(e2);
                                            }
                                        } else {
                                            //assert supportsAny || !newSuggested.trim().equalsIgnoreCase("any");
                                            if (!supportsAny && newSuggested.trim().equalsIgnoreCase("any")) {
                                                newEvents.add(eventFactory.createCharacters(availableVlans.getFirst()+""));
                                                LOG.debug("Rewrite suggested for manifest: NEWSUGGESTED ANY CHANGED supportsAny="+supportsAny+" origSuggested=" + origSuggested+" newSuggested="+newSuggested+" availableVlans.getCurrent()="+availableVlans.getFirst() + "  @ "+cmUrnString);
                                            } else {
                                                LOG.debug("Rewrite suggested for manifest: supportsAny="+supportsAny+" origSuggested=" + origSuggested+"  newSuggested="+newSuggested+"  @ "+cmUrnString);
                                                newEvents.add(eventFactory.createCharacters(newSuggested));
                                            }
                                        }
                                    } else
                                        newEvents.add(e2);
                                }
                            }
                            if (!handled)
                                newEvents.add(e);
                        } else
                            newEvents.add(e);

                    }
                    //replace existing extra Xml
                    newExtraXmls.add(new EventListExtraXml(newEvents));
                } else
                    newExtraXmls.add(extraXml);
            }

            modelRspec.setExtraXml(newExtraXmls);

            return origSuggested;
        } catch (XMLStreamException e) {
//            LOG.error(e);
            throw new RuntimeException(e);
        } catch (RspecParseException e) {
//            LOG.error(e);
            throw new RuntimeException(e);
        }
    }
    protected String getAvailableVlans(ModelRspec modelRspec)  {
        try {
            List<ExtraXml> origExtraXmls = modelRspec.getExtraXml();
            LOG.debug("getAvailableVlans entry extraXml.size()="+origExtraXmls.size()+" @ "+cmUrnString);

            for (ExtraXml extraXml : origExtraXmls) {
                LOG.debug("getAvailableVlans extraXml \""+extraXml.getStartElementName()+"\" @ "+cmUrnString);
                if (RspecXmlConstants.Q_STITCHING.equals(extraXml.getStartElementName())) {
                    assert extraXml instanceof EventListExtraXml;

                    String curPathName = null;
                    String curLinkUrn = null;

                    XMLEventReader eventReader = ((EventListExtraXml)extraXml).getAsEventReader();
                    while (eventReader.hasNext()) {
                        XMLEvent e = eventReader.nextEvent();
                        if (e.isStartElement()) {
                            boolean handled = false;
                            if (e.asStartElement().getName().equals(Q_STITCHING_PATH)) {
                                curPathName = e.asStartElement().getAttributeByName(Q_STITCHING_PATH_ID).getValue();
                            }
                            if (e.asStartElement().getName().equals(Q_STITCHING_LINK)) {
                                curLinkUrn = e.asStartElement().getAttributeByName(Q_STITCHING_LINK_ID).getValue();
                                LOG.debug("getAvailableVlans extraXml curLinkUrn=\""+curLinkUrn+"\"  <-> stitchIfaceUrn=\""+stitchIfaceUrn+"\" @ "+cmUrnString);
                            }
                            if (e.asStartElement().getName().equals(Q_STITCHING_VLAN_AVAILABLE) && curLinkUrn.equals(stitchIfaceUrn)) {
                                EventListExtraXml x = new EventListExtraXml(e.asStartElement(), eventReader);
                                XMLEventReader eventReader2 = x.getAsEventReader();
                                while (eventReader2.hasNext()) {
                                    XMLEvent e2 = eventReader2.nextEvent();
                                    if (e2.isCharacters())
                                        return e2.asCharacters().getData();
                                }
                            }
                            if (e.asStartElement().getName().equals(Q_STITCHING_VLAN_SUGGESTED) && curLinkUrn.equals(stitchIfaceUrn)) {

                            }
                        }

                    }
                }
            }

            return null;
        } catch (XMLStreamException e) {
//            LOG.error(e);
            throw new RuntimeException(e);
        } catch (RspecParseException e) {
//            LOG.error(e);
            throw new RuntimeException(e);
        }
    }

    public Integer getActiveVlan() {
        return activeVlan == null ? null : Integer.parseInt(activeVlan);
    }

    /** @returns total number of calls, this one included */
    public int registerCall(String callName) {
        return callCounts.compute(callName, (k, v) -> { if (v == null) return 1; else return v + 1; });
    }
    public int getCallCount(String callName) {
        return callCounts.getOrDefault(callName, new Integer(0));
    }
}
