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

import be.iminds.ilabt.jfed.rspec.model.DiskImage;
import be.iminds.ilabt.jfed.rspec.model.LinkSetting;
import be.iminds.ilabt.jfed.rspec.model.RspecInterface;
import be.iminds.ilabt.jfed.rspec.model.SliverType;
import be.iminds.ilabt.jfed.rspec.parser.ExtraXml;
import be.iminds.ilabt.jfed.rspec.parser.RspecParseException;
import be.iminds.ilabt.jfed.rspec.request.ext.delay.Pipe;
import be.iminds.ilabt.jfed.rspec.request.ext.delay.SliverTypeShaping;
import javafx.beans.Observable;
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.namespace.QName;
import javax.xml.stream.Location;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLStreamException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

/**
 * User: twalcari
 * Date: 4/3/2014
 * Time: 12:09
 */
public class DelayRspecNode extends FXRspecNode {
    private final ListProperty<FXLinkSetting> linkSettings =
            new SimpleListProperty<>(FXCollections.<FXLinkSetting>observableArrayList());

    public ListProperty<FXLinkSetting> getLinkSettings() {
        return linkSettings;
    }

    public FXLinkSetting getLinkSetting(RspecInterface fromIface, RspecInterface toIface) {
        assert (fromIface instanceof FXRspecInterface);
        assert (toIface instanceof FXRspecInterface);
        assert interfaces.contains(fromIface);
        assert interfaces.contains(toIface);
        for (FXLinkSetting ls : linkSettings) {
            if (ls.getFromIface() == fromIface && ls.getToIface() == toIface)
                return ls;
        }
        return null;
    }

    /**
     * remove unneeded link settings, and add all needed link settings
     */
    private void updateLinkSettings() {
        //remove unneeded link settings
        for (Iterator<FXLinkSetting> it = linkSettings.iterator(); it.hasNext(); ) {
            FXLinkSetting ls = it.next();
            if (!interfaces.contains(ls.getFromIface()) || !interfaces.contains(ls.getToIface())) {
                //remove linksetting
                it.remove();
            }
            if (ls.getFromIface() == ls.getToIface()) {
                //remove linksetting
                it.remove();
            }
        }

        //add all missing needed link settings
        for (FXRspecInterface fromIface : interfaces) {
            for (FXRspecInterface toIface : interfaces) {
                if (fromIface == toIface)
                    continue;
                boolean missing = true;
                for (FXLinkSetting ls : linkSettings)
                    if (ls.getFromIface() == fromIface && ls.getToIface() == toIface) {
                        missing = false;
                        break;
                    }
                if (missing) {
                    FXLinkSetting newLinkSetting = new FXLinkSetting(fromIface, toIface);
                    linkSettings.add(newLinkSetting);
                }
            }
        }
    }


    public DelayRspecNode(FXModelRspec modelRspec, String id) {
        super(modelRspec, id);
        initialize();
    }

    public DelayRspecNode(FXModelRspec newModelRspec, FXRspecNode rspecNode, InterfaceCopyMethod interfaceCopyMethod) {
        super(newModelRspec, rspecNode, interfaceCopyMethod);
        initialize();
    }

    public DelayRspecNode(FXModelRspec newModelRspec, FXRspecNode node) {
        this(newModelRspec, node, InterfaceCopyMethod.CREATE_NEW_INTERFACE);
    }

    private void initialize() {
        List<SliverType> sliverTypeList = new ArrayList<>();
        sliverTypeList.add(new ModifiedSliverType("delay", null, null, null, null, null));
        sliverTypes.setAll(sliverTypeList);
        interfaces.addListener((Observable observable) -> updateLinkSettings());
    }

    @Override
    public void setSliverTypeName(String name) {
        throw new UnsupportedOperationException("Sliver type of DelayRspecNode is fixed on 'delay', and cannot be changed");
    }

    @Override
    public void setSliverType(SliverType sliverType) {
        throw new UnsupportedOperationException("Sliver type of DelayRspecNode is fixed on 'delay', and cannot be changed");
    }


    void setPropertiesFromSliverTypeShaping(SliverTypeShaping sliverTypeShaping) throws RspecParseException {
        for (Pipe pipe : sliverTypeShaping.getPipe()) {
            FXRspecInterface sourceIface = getInterfaceByClientId(pipe.getSource());
            if (sourceIface == null)
                throw new RspecParseException(null, "Unknown source-iface '" + pipe.getSource() + "' detected in delay-pipe. All interfaces: " + getInterfaces());
            FXRspecInterface targetIface = getInterfaceByClientId(pipe.getDest());
            if (targetIface == null)
                throw new RspecParseException(null, "Unknown target-iface '" + pipe.getDest() + "' detected in delay-pipe. All interfaces: " + getInterfaces());

            FXLinkSetting linkSetting = getLinkSetting(sourceIface, targetIface);
            if (linkSetting == null) {
                linkSetting = new FXLinkSetting(sourceIface, targetIface);
                linkSettings.add(linkSetting);
            }

            if (pipe.getCapacity() != null)
                linkSetting.setCapacity_Kbps(new Long(pipe.getCapacity()));
            if (pipe.getLatency() != null)
                linkSetting.setLatency_ms(pipe.getLatency());
            if (pipe.getPacketLoss() != null)
                linkSetting.setPacketLoss(Double.valueOf(pipe.getPacketLoss()));
        }
    }

    private SliverTypeShaping getSliverTypeShaping() {
        SliverTypeShaping res = new SliverTypeShaping();

        for (LinkSetting linkSetting : linkSettings) {
            Pipe pipe = new Pipe();
            if (linkSetting.getFromIface() != null)
                pipe.setSource(linkSetting.getFromIface().getClientId());
            if (linkSetting.getToIface() != null)
                pipe.setDest(linkSetting.getToIface().getClientId());

            if (linkSetting.isCapacitySet())
                pipe.setCapacity(new Long(linkSetting.getCapacity_Kbps()).intValue());
            if (linkSetting.isLatencySet())
                pipe.setLatency(linkSetting.getLatency_ms());
            if (linkSetting.isPacketLossSet())
                pipe.setPacketLoss(new Double(linkSetting.getPacketLoss()).floatValue());
            res.getPipe().add(pipe);
        }
        return res;
    }

    //may not be changed for DelayRspecNode
    @Override
    public ObservableList<SliverType> getSliverTypes() {
        return /*FXCollections.unmodifiableObservableList(*/sliverTypes/*)*/;
    }

    private class ModifiedSliverType extends SliverType {
        ModifiedSliverType(String name, Collection<DiskImage> diskImages, Integer xenCores, Integer xenRam, Integer xenDisk, Collection<ExtraXml> other) {
            super(name, diskImages, xenCores, xenRam, xenDisk, other);
        }

        @Override
        public List<ExtraXml> getExtraXml() {
            List<ExtraXml> res = new ArrayList<>(super.getExtraXml());

            res.add(new ExtraXml() {
                @Override
                public void writeEvents(XMLEventWriter w) throws XMLStreamException {
                    JAXBElement<SliverTypeShaping> je =
                            new JAXBElement<>(FXRspecFactory.Q_SLIVER_TYPE_SHAPING, SliverTypeShaping.class, getSliverTypeShaping());

                    try {
                        JAXBContext jc = JAXBContext.newInstance(SliverTypeShaping.class);
                        Marshaller marshaller = jc.createMarshaller();
                        marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
                        marshaller.marshal(je, w);
                    } catch (JAXBException e) {
                        throw new RuntimeException("error writing SliverTypeShaping", e);
                    }
                }

                @Override
                public QName getStartElementName() {
                    return FXRspecFactory.Q_SLIVER_TYPE_SHAPING;
                }

                @Override
                public Location getLocation() {
                    return null;
                }
            });

            return res;
        }
    }
}
