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

import be.iminds.ilabt.jfed.rspec.model.BasicStringRspec;
import be.iminds.ilabt.jfed.rspec.model.RspecInterface;
import be.iminds.ilabt.jfed.rspec.model.RspecLink;
import be.iminds.ilabt.jfed.rspec.model.RspecNode;
import be.iminds.ilabt.jfed.rspec.parser.ExtraXml;
import be.iminds.ilabt.jfed.util.GeniUrn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;

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

    protected final BasicModelRspec modelRspec;

    protected String clientId;
    protected GeniUrn sliverId;
    protected String componentId;
    protected String macAddress;
    protected Integer felixVlan;

    protected String componentName;
    protected String role;
    protected String publicIpv4;

    protected final List<ExtraXml> extraXml = new ArrayList<>();
    protected final List<ExtraXml> extraXmlForRef = new ArrayList<>();
    protected final List<IpAddress> ipAddresses = new ArrayList<>();
    protected BasicRspecNode node;
    protected BasicRspecLink link;

    /**
     * create an interface with the link unbound. You need to call bindLink to have a valid RspecInterface
     *
     * @param node
     * @param anyId
     */
    BasicRspecInterface(BasicRspecNode node, String anyId) {
        assert node != null;

        this.modelRspec = node.getRspec();
        this.node = node;
//         this.clientId = clientId;todo

        //check if not already added
        for (RspecInterface existingIface : node.getInterfaces())
            if (existingIface == this || this.getUniqueId().equals(existingIface.getUniqueId()))
                throw new RuntimeException("Duplicate interfaces in XML Rspec: id=\"" + existingIface.getUniqueId() + "\"");
        //this should check all the interfaces in the whole modelRspec!

        node.addInterface(this);

        //since this constructor is used when parsing, component_manager_id is not set automatically
    }

    /**
     * create an interface with the node unbound. You need to call bindNode to have a valid RspecInterface
     *
     * @param link
     * @param clientId
     */
    BasicRspecInterface(BasicRspecLink link, String clientId) {
        assert link != null;

        this.modelRspec = link.getRspec();
        this.node = null;
        this.link = link;
        this.clientId = clientId;

        //check if not already added
        for (RspecInterface existingIface : link.getInterfaces())
            if (existingIface == this || this.getUniqueId().equals(existingIface.getUniqueId()))
                throw new RuntimeException("Duplicate interfaces in XML Rspec: id=\"" + existingIface.getUniqueId() + "\"");
        //this should check all the interfaces in the whole modelRspec!

        link.addInterface(this);
    }

    BasicRspecInterface(BasicRspecNode node, BasicRspecLink link, String clientId) {
        assert link != null;
        assert node != null;
        assert node.getRspec() == link.getRspec();

        this.modelRspec = node.getRspec();
        this.node = node;
        this.link = link;
        this.clientId = clientId;

        //check if not already added
        for (RspecInterface existingIface : node.getInterfaces())
            if (existingIface == this || this.getUniqueId().equals(existingIface.getUniqueId()))
                throw new RuntimeException("Duplicate interfaces in XML Rspec: id=\"" + existingIface.getUniqueId() + "\"");
        for (RspecInterface existingIface : link.getInterfaces())
            if (existingIface == this || this.getUniqueId().equals(existingIface.getUniqueId()))
                throw new RuntimeException("Duplicate interfaces in XML Rspec: id=\"" + existingIface.getUniqueId() + "\"");
        //this should check all the interfaces in the whole modelRspec!

        node.addInterface(this);
        link.addInterface(this);

        if (node.getComponentManagerId() != null &&
                !link.getComponentManagerUrns().contains(node.getComponentManagerId()))
            link.getComponentManagerUrns().add(node.getComponentManagerId());
    }

    /**
     * overwrites everything,e except for bound link and node
     *
     * @param other
     * @param overwriteWithNull
     */
    @Override
    public void overwritePropertiesWith(RspecInterface other, boolean overwriteWithNull) {
        if (overwriteWithNull || other.getSliverId() != null)
            this.sliverId = other.getSliverId();
        if (overwriteWithNull || other.getClientId() != null)
            this.clientId = other.getClientId();
        if (overwriteWithNull || other.getComponentId() != null)
            this.componentId = other.getComponentId();

        if (overwriteWithNull || other.getComponentName() != null)
            this.componentName = other.getComponentName();
        if (overwriteWithNull || other.getRole() != null)
            this.role = other.getRole();
        if (overwriteWithNull || other.getPublicIpv4() != null)
            this.publicIpv4 = other.getPublicIpv4();

        if (overwriteWithNull || other.getMacAddress() != null)
            this.macAddress = other.getMacAddress();
        if (overwriteWithNull || other.getFelixVlan() != null)
            this.felixVlan = other.getFelixVlan();
        if (overwriteWithNull || other.getIpAddresses() != null && !other.getIpAddresses().isEmpty()) {
            this.ipAddresses.clear();
            this.ipAddresses.addAll(other.getIpAddresses());
        }
        if (overwriteWithNull || other.getExtraXml() != null && !other.getExtraXml().isEmpty()) {
            this.extraXml.clear();
            this.extraXml.addAll(other.getExtraXml());
        }
        if (overwriteWithNull || other.getRefExtraXml() != null && !other.getRefExtraXml().isEmpty()) {
            this.extraXmlForRef.clear();
            this.extraXmlForRef.addAll(other.getRefExtraXml());
        }
    }

    @Override
    public void bindLink(RspecLink rspecLink) {
        assert rspecLink instanceof BasicRspecLink;

        if (this.node == null || this.link != null)
            throw new IllegalStateException("bindLink can only be called once, and after the matching constructor");

        this.link = (BasicRspecLink) rspecLink;

        //check if not already added
        for (RspecInterface existingIface : link.getInterfaces())
            if (existingIface == this || this.getClientId().equals(existingIface.getClientId()))
                throw new RuntimeException("Duplicate interfaces in XML Rspec: id=\"" + existingIface.getClientId() + "\"");
        //this should check all the interfaces in the whole modelRspec!

        link.addInterface(this);

    }

    @Override
    public void bindNode(RspecNode rspecNode) {
        assert rspecNode instanceof BasicRspecNode;

        if (this.node != null || this.link == null)
            throw new IllegalStateException("bindNode can only be called once, and after the matching constructor");

        this.node = (BasicRspecNode) rspecNode;

        //check if not already added
        for (RspecInterface existingIface : node.getInterfaces())
            if (existingIface == this || this.getClientId().equals(existingIface.getClientId()))
                throw new RuntimeException("Duplicate interfaces in XML Rspec: id=\"" + existingIface.getClientId() + "\"");
        //this should check all the interfaces in the whole modelRspec!

        node.addInterface(this);
    }

    @Override
    public void moveToNode(RspecNode newRspecNode) {
        RspecNode oldRspecNode = this.node;
        assert this.node != null;
        assert oldRspecNode instanceof BasicRspecNode;
        assert newRspecNode instanceof BasicRspecNode;

        if (this.node == null)
            throw new IllegalStateException("moveToNode can only be called an interface that is bound to a node");

        this.node = (BasicRspecNode) newRspecNode;

        //check if not already added
        for (RspecInterface existingIface : newRspecNode.getInterfaces())
            if (existingIface == this || this.getClientId().equals(existingIface.getClientId()))
                throw new RuntimeException("Duplicate interfaces in XML Rspec: id=\"" + existingIface.getClientId() + "\"");

        ((BasicRspecNode) oldRspecNode).removeInterface(this);
        ((BasicRspecNode) newRspecNode).addInterface(this);
    }

    @Override
    public void moveToLink(RspecLink newRspecLink) {
        RspecLink oldRspecLink = this.link;
        assert this.link != null;
        assert oldRspecLink instanceof BasicRspecLink;
        assert newRspecLink instanceof BasicRspecLink;

        if (this.link == null)
            throw new IllegalStateException("moveToLink can only be called an interface that is bound to a link");

        this.link = (BasicRspecLink) newRspecLink;

        //check if not already added
        for (RspecInterface existingIface : newRspecLink.getInterfaces())
            if (existingIface == this || this.getClientId().equals(existingIface.getClientId()))
                throw new RuntimeException("Duplicate interfaces in XML Rspec: id=\"" + existingIface.getClientId() + "\"");

        ((BasicRspecLink) oldRspecLink).removeInterface(this);
        ((BasicRspecLink) newRspecLink).addInterface(this);
    }

    @Override
    public boolean isBound() {
        return node != null && link != null;
    }

    @Override
    public boolean isUnbound() {
        return node == null || link == null;
    }

    @Override
    public boolean isLinkUnbound() {
        return link == null;
    }

    @Override
    public boolean isNodeUnbound() {
        return node == null;
    }

    @Override
    public boolean isLinkBound() {
        return link != null;
    }

    @Override
    public boolean isNodeBound() {
        return node != null;
    }

    @Override
    public String getUniqueId() {
        return BasicStringRspec.makeUniqueInterfaceId(
                clientId,
                componentId,
                node == null ? null : node.getUniqueId()
        );
    }

    @Override
    public String getClientId() {
        return clientId;
    }

    @Override
    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    @Override
    public String getComponentId() {
        return componentId;
    }

    @Override
    public void setComponentId(String componentId) {
        this.componentId = componentId;
    }

    @Override
    public String getPublicIpv4() {
        return publicIpv4;
    }

    public void setPublicIpv4(String publicIpv4) {
        this.publicIpv4 = publicIpv4;
    }

    @Override
    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }

    @Override
    public String getComponentName() {
        return componentName;
    }

    public void setComponentName(String componentName) {
        this.componentName = componentName;
    }

    @Override
    public GeniUrn getSliverId() {
        return sliverId;
    }

    @Override
    public void setSliverId(String sliverId) {
        if (sliverId == null) {
            this.sliverId = null;
            return;
        }

        GeniUrn geniUrn = GeniUrn.parse(sliverId);

        if (geniUrn != null) {
            this.sliverId = geniUrn;
        } else {
            LOG.warn("Could not set sliverId to {} as it is not a valid URN!", sliverId);
            this.sliverId = null;
        }
    }

    @Override
    public void setSliverId(GeniUrn sliverId) {
        this.sliverId = sliverId;

    }

    @Override
    public String getMacAddress() {
        return macAddress;
    }

    @Override
    public void setMacAddress(String value) {
        this.macAddress = value;
    }

    @Override
    public Integer getFelixVlan() {
        return felixVlan;
    }

    @Override
    public void setFelixVlan(Integer felixVlan) {
        this.felixVlan = felixVlan;
    }

    @Override
    public List<IpAddress> getIpAddresses() {
        return ipAddresses;
    }

    @Override
    public List<ExtraXml> getExtraXml() {
        return extraXml;
    }

    @Override
    public List<ExtraXml> getRefExtraXml() {
        return extraXmlForRef;
    }

    @Override
    public BasicRspecNode getNode() {
        if (this.node == null)
            throw new IllegalStateException("bindNode was not yet called");
        return node;
    }

    @Override
    public BasicRspecLink getLink() {
        if (this.link == null)
            throw new IllegalStateException("bindLink was not yet called");
        return link;
    }

    @Override
    public boolean delete() {
        //remove partially bound interfaces without error, and return true
        //check if this interface was already removed from the model, return if necessary
        if (link == null && node == null) {
            return false;
        }

        if (node != null) {
            boolean removed = node.removeInterface(this);
            assert removed;
        }
        if (link != null) {
            boolean removed = link.removeInterface(this);
            assert removed;
        }
        node = null;
        link = null;

        return true;
    }

    @Override
    public BasicModelRspec getRspec() {
        return modelRspec;
    }

    @Override
    public String toString() {
        return "RspecInterface{id=" + clientId + '}';
    }
}
