package be.iminds.ilabt.jfed.rspec.convert;

import be.iminds.ilabt.jfed.rspec.model.*;
import be.iminds.ilabt.jfed.rspec.parser.ExtraXml;
import be.iminds.ilabt.jfed.rspec.parser.RspecXmlConstants;
import be.iminds.ilabt.jfed.util.GeniUrn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collections;
import java.util.ListIterator;
import java.util.Objects;

/**
 * RspecConverter
 */
public class RspecConverter {
    private static final Logger LOG = LoggerFactory.getLogger(RspecConverter.class);

    /**
     * Change the provided manifest RSpec into a request RSpec.
     *
     * @param source the Rspec to change. The original is NOT changed.
     * @param keepNodeBindings keep bound nodes are not
     * @return a new request ModelRspec
     */
    public ModelRspec convertManifestToRequest(ModelRspec source, boolean keepNodeBindings, RspecFactory rspecFactory) {
        if (Objects.equals(source.getType(), "request")) {
            LOG.warn("convertManifestToRequest was asked to convert a request rspec. This will be ignored, and we will act as if it was a manifest.");
        }
        if (Objects.equals(source.getType(), "advertisement")) {
            LOG.warn("convertManifestToRequest was asked to convert an advertisement rspec. This will be ignored, and we will act as if it was a manifest.");
        }
        ModelRspec res = rspecFactory.copyModelRspec(source, "request");

        //remove manifest specific stuff (and add request specific stuff?)
        for (RspecNode node : res.getNodes()) {
            node.setComponentName(null);
            if (!keepNodeBindings)
                node.setComponentId((String) null);
            node.setLoginServices(Collections.emptyList());
            node.setSliverId((String)null);

            for (ListIterator<ExtraXml> it = node.getExtraXml().listIterator(); it.hasNext();) {
                ExtraXml extraXml = it.next();
                if (Objects.equals(RspecXmlConstants.Q_NODE_HOST, extraXml.getStartElementName()))
                    it.remove();
                if (Objects.equals(RspecXmlConstants.Q_NODE_VNODE, extraXml.getStartElementName()))
                    it.remove();
            }

            for (RspecInterface iface : node.getInterfaces()) {
                iface.setSliverId((String) null);
                iface.setMacAddress(null);
                iface.setComponentId(null); //note that node bindings can be kept by this method, but interface bindings can not. (add that functionality if needed)
            }
        }

        for (RspecLink link : res.getLinks()) {
            link.setSliverId((String) null);
            link.setVlanTag(null); //keeping vlantags upsets some testbeds

            for (RspecInterface iface : link.getInterfaces()) {
                iface.setSliverId((String) null);
                iface.setComponentId(null); //note that node bindings can be kept by this method, but interface bindings can not. (add that functionality if needed)
                iface.setMacAddress(null); //does not actually appear in manifest
            }
        }
        for (ListIterator<ExtraXml> it = res.getExtraXml().listIterator(); it.hasNext();) {
            ExtraXml extraXml = it.next();
            //remove stitching data. It makes no sense in user request. (SCS adds it)
            if (Objects.equals(extraXml.getStartElementName(), RspecXmlConstants.Q_STITCHING))
                it.remove();
        }

        return res;
    }

    /**
     * Create a copy in which the Component manager bindings of all nodes, links and interfaces are removed.
     * @param source
     * @param rspecFactory
     * @return
     */
    public ModelRspec removeComponentManagerBindings(ModelRspec source, RspecFactory rspecFactory) {
        ModelRspec res = rspecFactory.copyModelRspec(source, null);

        //remove manifest specific stuff (and add request specific stuff?)
        for (RspecNode node : res.getNodes()) {
            node.setComponentName(null);
            node.setComponentId((String) null);
            node.setComponentManagerId((String) null);

            for (RspecInterface iface : node.getInterfaces()) {
                iface.setComponentId(null);
            }
        }

        for (RspecLink link : res.getLinks()) {
            link.getComponentManagerUrns().clear();

            for (RspecInterface iface : link.getInterfaces()) {
                iface.setComponentId(null);
            }
        }

        return res;
    }

    /**
     * Create a copy in which the Component manager bindings of all nodes, links and interfaces are forced to a single CM..
     * Note that this will make multi CM RSpecs into single CM RSpecs
     *
     * @param source
     * @param rspecFactory
     * @param componentManagerUrn the component manager URN to force to all nodes and links
     * @return
     */
    public ModelRspec forceComponentManagerBindings(ModelRspec source, RspecFactory rspecFactory, GeniUrn componentManagerUrn) {
        ModelRspec res = rspecFactory.copyModelRspec(source, null);

        //remove manifest specific stuff (and add request specific stuff?)
        for (RspecNode node : res.getNodes()) {
            node.setComponentManagerId(componentManagerUrn);

            if (node.getComponentId() != null && !Objects.equals(node.getComponentId().getTopLevelAuthority(), componentManagerUrn.getTopLevelAuthority())) {
                node.setComponentId((String) null);
                node.setComponentName(null);
            }

            for (RspecInterface iface : node.getInterfaces()) {
                if (iface.getComponentId() != null) {
                    GeniUrn ifaceCompIdUrn = GeniUrn.parse(iface.getComponentId());
                    if (ifaceCompIdUrn == null)
                        iface.setComponentId(null);
                    else {
                        if (!Objects.equals(ifaceCompIdUrn.getTopLevelAuthority(), componentManagerUrn.getTopLevelAuthority())) {
                            iface.setComponentId(null);
                        }
                    }
                }
            }
        }

        for (RspecLink link : res.getLinks()) {
            link.getComponentManagerUrns().clear();
            link.getComponentManagerUrns().add(componentManagerUrn);

            for (RspecInterface iface : link.getInterfaces()) {
                if (iface.getComponentId() != null) {
                    GeniUrn ifaceCompIdUrn = GeniUrn.parse(iface.getComponentId());
                    if (ifaceCompIdUrn == null)
                        iface.setComponentId(null);
                    else {
                        if (!Objects.equals(ifaceCompIdUrn.getTopLevelAuthority(), componentManagerUrn.getTopLevelAuthority())) {
                            iface.setComponentId(null);
                        }
                    }
                }
            }
        }

        return res;
    }
}
