package be.iminds.ilabt.jfed.rspec.model.impl;

import be.iminds.ilabt.jfed.lowlevel.api.user_spec.UserSpec;
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.util.GeniUrn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import javax.xml.stream.XMLEventWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * BasicModelRspecFactory
 */
public class BasicRspecFactory implements RspecFactory {

    private final static Logger LOG = LoggerFactory.getLogger(BasicRspecFactory.class);

    private static final BasicRspecFactory INSTANCE = new BasicRspecFactory();

    public static BasicRspecFactory getInstance() {
        return INSTANCE;
    }

    private BasicRspecFactory() {
    }


    @Override
    public ModelRspecType getModelRspecType() {
        return ModelRspecType.BASIC;
    }

    @Override
    public BasicRspecNode createNode(ModelRspec modelRspec) {
        assert modelRspec == null || modelRspec instanceof BasicModelRspec : modelRspec.getClass().getName();
        return new BasicRspecNode((BasicModelRspec) modelRspec, null);
    }

    @Override
    public BasicRspecNode createNodeWithClientId(ModelRspec modelRspec, String id) {
        assert modelRspec == null || modelRspec instanceof BasicModelRspec : modelRspec.getClass().getName();
        return new BasicRspecNode((BasicModelRspec) modelRspec, id);
    }

    @Override
    public BasicRspecNode copyNode(ModelRspec newModelRspec, RspecNode rspecNode, RspecNode.InterfaceCopyMethod interfaceCopyMethod) {
        assert newModelRspec instanceof BasicModelRspec;
        assert rspecNode instanceof BasicRspecNode;
        return new BasicRspecNode((BasicModelRspec) newModelRspec, rspecNode, interfaceCopyMethod);
    }

    @Override
    public BasicRspecNode copyNodeIncludingInterfaces(ModelRspec newModelRspec, RspecNode rspecNode) {
        assert newModelRspec instanceof BasicModelRspec;
        assert rspecNode instanceof BasicRspecNode;
        return new BasicRspecNode((BasicModelRspec) newModelRspec, rspecNode, RspecNode.InterfaceCopyMethod.CREATE_NEW_INTERFACE);
    }

    @Override
    public RspecNode convertToSpecialNode(RspecNode origNode) throws RspecParseException {
        assert origNode != null;
        assert origNode instanceof BasicRspecNode;

        //no special nodes in BasicRspecFactory  (delaynode is FX specific)

        return origNode;
    }

    @Override
    public BasicRspecLink createLink(ModelRspec modelRspec, String addDefaultLinkType) {
        assert modelRspec instanceof BasicModelRspec;
        return new BasicRspecLink((BasicModelRspec) modelRspec, null, addDefaultLinkType);
    }

    @Override
    public BasicRspecLink createLinkWithClientId(ModelRspec modelRspec, String id, String addDefaultLinkType) {
        assert modelRspec instanceof BasicModelRspec;
        return new BasicRspecLink((BasicModelRspec) modelRspec, id, addDefaultLinkType);
    }

    @Override
    public BasicRspecLink copyLink(ModelRspec newModelRspec, RspecLink rspecLink, RspecNode.InterfaceCopyMethod interfaceCopyMethod) {
        assert newModelRspec instanceof BasicModelRspec;
        return new BasicRspecLink((BasicModelRspec) newModelRspec, rspecLink, interfaceCopyMethod);
    }

    @Override
    public BasicRspecInterface createInterface(RspecNode node, String id) {
        assert node instanceof BasicRspecNode;
        return new BasicRspecInterface((BasicRspecNode) node, id);
    }

    @Override
    public BasicRspecInterface createInterface(RspecLink link, String id) {
        assert link instanceof BasicRspecLink;
        return new BasicRspecInterface((BasicRspecLink) link, id);
    }

    @Override
    public BasicRspecInterface createInterface(RspecNode node, RspecLink link, String id) {
        assert node instanceof BasicRspecNode;
        assert link instanceof BasicRspecLink;
        return new BasicRspecInterface((BasicRspecNode) node, (BasicRspecLink) link, id);
    }

    @Override
    public BasicRspecInterface createInterface(RspecNode node, RspecLink link) {
        assert node instanceof BasicRspecNode;
        assert link instanceof BasicRspecLink;
        assert node.getRspec() == link.getRspec();
        assert node.getRspec() instanceof BasicModelRspec;
        String newClientId = node.getRspec().nextIfaceName(node);
        return new BasicRspecInterface((BasicRspecNode) node, (BasicRspecLink) link, newClientId);
    }

    @Override
    public LinkSetting createLinkSetting(RspecInterface fromIface, RspecInterface toIface) {
        assert fromIface instanceof BasicRspecInterface;
        assert toIface instanceof BasicRspecInterface;
        return new BasicLinkSetting((BasicRspecInterface) fromIface, (BasicRspecInterface) toIface);
    }

    @Override
    public ExecuteService createExecuteService(String shell, String command) {
        return new BasicExecuteService(shell, command);
    }

    @Override
    public InstallService createInstallService(String installPath, String url) {
        return new BasicInstallService(installPath, url);
    }

    @Override
    public AnsibleService createAnsibleService() {
        return new BasicAnsibleService();
    }

    @Override
    public BasicModelRspec createModelRspec(String rspecType) {
        return new BasicModelRspec(rspecType);
    }

    @Override
    public BasicModelRspec copyModelRspec(ModelRspec orig, @Nullable String newType) {
        //////////////////////////////////////////////////////////////////
        //WARNING: This is cut-and-paste reused in FXRspecFactory!
        //////////////////////////////////////////////////////////////////
        BasicModelRspec res = createModelRspec(newType == null ? orig.getType() : newType);

        res.setParseData(orig.getSchemaLocation(), orig.getOrigDefaultNamespace(), orig.getNameSpaceInfo());

        for (RspecNode n : orig.getNodes()) {
            res.addNode(new BasicRspecNode(res, n, RspecNode.InterfaceCopyMethod.NO_COPY));
        }

        //copy unbound interfaces
        for (RspecNode n : orig.getNodes()) {
            BasicRspecNode copiedNode = res.getNodeByUniqueId(n.getUniqueId());
            assert copiedNode != null;
            //clone interface
            // since we copy to new model, just copy interface name
            n.getInterfaces().stream()
                    .filter(iface -> !iface.isLinkBound())
                    .forEach(iface -> {
                        //clone interface
                        BasicRspecInterface ifaceCopy =
                                new BasicRspecInterface(copiedNode, iface.getClientId()); //since we copy to new model, just copy interface name
                        ifaceCopy.overwritePropertiesWith(iface, true);
                    });
        }

        for (RspecLink l : orig.getLinks()) {
            BasicRspecLink newLink = new BasicRspecLink(res, l, RspecNode.InterfaceCopyMethod.CREATE_NEW_INTERFACE);
            res.addLink(newLink);
            for (RspecInterface i : l.getInterfaces()) {
                BasicRspecInterface newIface = newLink.getInterfaceByClientId(i.getClientId());
                BasicRspecNode newNode = i.getNode() == null ? null : res.getNodeByUniqueId(i.getNode().getUniqueId());
                newIface.bindNode(newNode);
                newIface.overwritePropertiesWith(i, true);
            }
        }

        for (OpenflowDataPath odp : orig.getOpenflowDataPaths()) {
            OpenflowDataPath newOdp =
                    new BasicOpenflowDataPath(odp.getAsExtraXml(), odp.getComponentManagerId(), odp.getComponentId(), odp.getDpid());
            res.addOpenflowDataPath(newOdp);
        }

        for (Lease l : orig.getLeases()) {
            res.addLease(new BasicLease(l));
        }
        for (Channel channel : orig.getChannels()) {
            res.addChannel(new BasicChannel(channel));
        }
        for (AddressPool addressPool : orig.getAddressPools()) {
            res.addAddressPool(new BasicAddressPool(addressPool));
        }

        List<ExtraXml> extraXmlCopy = new ArrayList<>();
        //no copy. That should be ok, as only parser will normally change this and nothing after that.
        extraXmlCopy.addAll(orig.getExtraXml());
        res.setExtraXml(extraXmlCopy);

        List<UserSpec> userSpecCopy = orig.getSshKeys().stream().map(UserSpec::new).collect(Collectors.toList());
        res.setSshKeys(userSpecCopy);

        res.setRoutableAddressesAvailable(orig.getRoutableAddressesAvailable());
        res.setRoutableAddressesConfigured(orig.getRoutableAddressesConfigured());

        res.setExpires(orig.getExpiresString());

        return res;
        //////////////////////////////////////////////////////////////////
        //WARNING: This is cut-and-paste reused in FXRspecFactory!
        //////////////////////////////////////////////////////////////////
    }

    @Override
    public boolean processExtraRspecXml(ModelRspec model, EventListExtraXml extraXml) throws RspecParseException {
        return false;
    }

    @Override
    public boolean processExtraNodeXml(RspecNode node, EventListExtraXml extraXml) throws RspecParseException {
        return false;
    }

    @Override
    public boolean processExtraLinkXml(RspecLink link, EventListExtraXml extraXml) throws RspecParseException {
        return false;
    }

    @Override
    public void writeExtraRspecXml(ModelRspec modelRspec, XMLEventWriter eventWriter) {
    }

    @Override
    public void writeExtraNodeXml(RspecNode node, XMLEventWriter eventWriter) {
    }

    @Override
    public void writeExtraLinkXml(RspecLink link, XMLEventWriter eventWriter) {
    }


    @Override
    public boolean equals(Object o) {
        return o instanceof BasicRspecFactory;
    }

    @Override
    public int hashCode() {
        return BasicRspecFactory.class.hashCode();
    }

    @Override
    public void mergeExtra(ModelRspec mergedModel, ModelRspec partialRspec) {
        //nothing to do (or we have to merge the barriers using extraXML)

        partialRspec.getChannels().forEach(mergedModel::addChannel);
        partialRspec.getLeases().forEach(mergedModel::addLease);
    }

    @Override
    public void finishParsing(ModelRspec modelRspec) {

    }

    @Override
    public OpenflowDataPath createOpenflowDataPath(ModelRspec modelRspec, EventListExtraXml allXml, String componentManagerId, String componentId, String dpidAttValue) {
        assert modelRspec instanceof BasicModelRspec;
        return new BasicOpenflowDataPath(allXml, componentManagerId, componentId, dpidAttValue);
    }

    @Override
    public Lease createLease(ModelRspec rspec, String leaseId, String clientId, Date validFromDate, Date validUntilDate, String validFromString, String validUntilString) {
        assert rspec instanceof BasicModelRspec;
        return new BasicLease(leaseId, clientId, validFromDate, validUntilDate, validFromString, validUntilString);
    }

    @Override
    public Lease createLease(ModelRspec rspec, String leaseId, String clientId, Date validFromDate, Date validUntilDate) {
        assert rspec instanceof BasicModelRspec;
        return new BasicLease(leaseId, clientId, validFromDate, validUntilDate);
    }

    @Override
    public Channel createChannel(ModelRspec modelRspec) {
        assert modelRspec instanceof BasicModelRspec;
        return new BasicChannel();
    }

    @Override
    public GeantTestbedType createGeantTestbedType(String name) {
        return new BasicGeantTestbedType(name);
    }

    @Override
    public AddressPool createAddressPool(ModelRspec modelRspec) {
        assert modelRspec instanceof BasicModelRspec;
        return new BasicAddressPool();
    }

    @Override
    public AddressPool copyAddressPool(ModelRspec modelRspec, AddressPool addressPool) {
        assert modelRspec instanceof BasicAddressPool;
        return new BasicAddressPool(addressPool);
    }

    @Override
    public IPv4 createIPv4(ModelRspec modelRspec) {
        assert modelRspec instanceof BasicModelRspec;
        return new BasicIPv4();
    }

    @Override
    public IPv4 createIPv4(ModelRspec modelRspec, String address, String netmask) {
        assert modelRspec instanceof BasicModelRspec;
        return new BasicIPv4(address, netmask);
    }

    @Override
    public ExecuteAnsiblePlaybook createExecuteAnsibleCookbook(ModelRspec rspec, String source, String outputFilename) {
        return new BasicExecuteAnsiblePlaybook(source, outputFilename);
    }

    @Override
    public DistributeSshKeypair createDistributeSshKeypair(ModelRspec rspec, String location, GeniUrn userUrn) {
        return new BasicDistributeSshKeypair(location, userUrn);
    }
}
