package be.iminds.ilabt.jfed.rspec.model.javafx_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 javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.List;

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

    protected final FXModelRspec modelRspec;

    /**
     * Will only be set in ManifestRspec
     */
    protected final ObjectProperty<GeniUrn> sliverId = new SimpleObjectProperty<>();
    private final List<ExtraXml> extraXml = new ArrayList<>();
    private final List<ExtraXml> extraXmlForRef = new ArrayList<>();
    private final StringProperty clientId = new SimpleStringProperty();
    private final StringProperty componentId = new SimpleStringProperty();
    private final StringProperty componentName = new SimpleStringProperty();
    private final StringProperty role = new SimpleStringProperty();
    private final StringProperty publicIpv4 = new SimpleStringProperty();
    private final StringProperty macAddress = new SimpleStringProperty();
    private final ObjectProperty<Integer> felixVlan = new SimpleObjectProperty<>(null);
    private final ListProperty<IpAddress> ipAddresses = new SimpleListProperty<>(FXCollections.<IpAddress>observableArrayList());
    private FXRspecNode node;
    private FXRspecLink link;

    /**
     * create an interface with the link unbound. You need to call bindLink to have a valid RspecInterface
     */
    FXRspecInterface(@Nonnull FXRspecNode node, String clientId) {
        this.modelRspec = node.getRspec();
        this.node = node;
        this.clientId.setValue(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() + "\"");
        //this should check all the interfaces in the whole modelRspec!

        node.addInterface(this);

        //check accessor correctness
        assert !isNodeUnbound();
        assert isLinkUnbound();
        assert !isBound();
        //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
     */
    FXRspecInterface(@Nonnull FXRspecLink link, String clientId) {
        this.modelRspec = link.getRspec();
        this.node = null;
        this.link = link;
        this.clientId.setValue(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);

        //check accessor correctness
        assert isNodeUnbound();
        assert !isLinkUnbound();
        assert !isBound();
    }
    FXRspecInterface(@Nonnull FXRspecNode node, @Nonnull FXRspecLink link, String clientId) {
        assert node.getRspec() == link.getRspec();

        this.modelRspec = node.getRspec();
        this.node = node;
        this.link = link;
        this.clientId.setValue(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());

        //check accessor correctness
        assert !isNodeUnbound();
        assert !isLinkUnbound();
        assert isBound();
    }

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

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

        if (overwriteWithNull || other.getMacAddress() != null)
            this.macAddress.setValue(other.getMacAddress());
        if (overwriteWithNull || other.getFelixVlan() != null)
            this.felixVlan.setValue(other.getFelixVlan());
        if (overwriteWithNull || other.getIpAddresses() != null && !other.getIpAddresses().isEmpty())
            this.ipAddresses.setAll(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 FXRspecLink;

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

        this.link = (FXRspecLink) 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 FXRspecNode;

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

        this.node = (FXRspecNode) 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 FXRspecNode;
        assert newRspecNode instanceof FXRspecNode;

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

        this.node = (FXRspecNode) 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() + "\"");

        ((FXRspecNode) oldRspecNode).removeInterface(this);
        ((FXRspecNode) newRspecNode).addInterface(this);
    }
    @Override
    public void moveToLink(RspecLink newRspecLink) {
        RspecLink oldRspecLink = this.link;
        assert this.link != null;
        assert oldRspecLink instanceof FXRspecLink;
        assert newRspecLink instanceof FXRspecLink;

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

        this.link = (FXRspecLink) 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() + "\"");

        ((FXRspecLink) oldRspecLink).removeInterface(this);
        ((FXRspecLink) 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.get(),
                componentId.get(),
                node == null ? null : node.getUniqueId()
        );
    }

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

    public StringProperty clientIdProperty() {
        return clientId;
    }

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

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

    public StringProperty componentIdProperty() {
        return componentId;
    }

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

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

    public StringProperty componentNameProperty() {
        return componentName;
    }

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

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

    public StringProperty roleProperty() {
        return role;
    }

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

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

    public StringProperty publicIpv4Property() {
        return publicIpv4;
    }

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

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

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

        GeniUrn geniUrn = GeniUrn.parse(sliverId);

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

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

    public ObjectProperty<GeniUrn> sliverIdProperty() {
        return sliverId;
    }

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

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

    public StringProperty macAddressProperty() {
        return macAddress;
    }

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

    public ObjectProperty<Integer> felixVlanProperty() {
        return felixVlan;
    }

    public void setFelixVlan(Integer felixVlan) {
        this.felixVlan.setValue(felixVlan);
    }

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

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

    @Override
    public FXRspecNode getNode() {
        if (this.node == null)
            throw new IllegalStateException("bindNode was not yet called for interface with clientId="+clientId);
        return node;
    }

    @Override
    public FXRspecLink getLink() {
        if (this.link == null)
            throw new IllegalStateException("bindLink was not yet called for interface with clientId="+clientId);
        return link;
    }


    @Override
    public boolean delete() {
//        System.out.println("RSpecInterface(\""+getId()+"\").delete() node="+node+" link="+link+" link interfaces:");
//        if (link != null) {
//            for (RspecInterface iface : link.getInterfaces()) {
//                System.out.println("  iface="+iface.getId());
//            }
//        }

        //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 (link != null) {
            boolean removed = link.removeInterface(this);
            assert removed == true;
        }
        if (node != null) {
            boolean removed = node.removeInterface(this);
            assert removed == true;
        }

        node = null;
        link = null;

        return true;
    }

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

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

}
