package be.iminds.ilabt.jfed.gts;

import be.iminds.ilabt.jfed.groovy_dsl.DSLEventFactory;
import be.iminds.ilabt.jfed.groovy_dsl.DSLEventWriter;
import be.iminds.ilabt.jfed.groovy_dsl.DSLOutputFactory;
import be.iminds.ilabt.jfed.groovy_dsl.DSLStreamException;
import be.iminds.ilabt.jfed.groovy_dsl.events.ClosureRefArgument;
import be.iminds.ilabt.jfed.groovy_dsl.helpers.IndentingDSLEventWriter;
import be.iminds.ilabt.jfed.groovy_dsl.impl.events.ClosureRefArgumentImpl;
import be.iminds.ilabt.jfed.gts.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.StringWriter;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable;

import static be.iminds.ilabt.jfed.gts.GtsDslConstants.*;

/**
 * User: twalcari
 * Date: 5/18/2015
 * Time: 10:50
 */
public class GtsDslWriter implements Callable<String> {
    private static final Logger LOG = LoggerFactory.getLogger(GtsDslWriter.class);
    private static final String[] LINK_PORT_IDS = {"src", "dst"};

    private final GtsType model;

    public GtsDslWriter(GtsType model) {
        this.model = model;
    }


    @Override
    public String call() throws DSLStreamException {
        DSLOutputFactory outputFactory = DSLOutputFactory.newInstance();
        StringWriter sw = new StringWriter();

        DSLEventWriter eventWriter = outputFactory.createDSLEventWriter(sw);
        eventWriter = new IndentingDSLEventWriter(eventWriter);

        DSLEventFactory eventFactory = DSLEventFactory.newInstance();

        eventWriter.add(eventFactory.createClosureStartElement(
                (model.isDefinition() ? "type ": "")
                        + model.getName()));

        for (GtsNode node : model.getNodes()) {
            if (node instanceof GtsPort) {
                writePort((GtsPort) node, eventWriter, eventFactory);
            } else if (node instanceof GtsOfx) {
                writeOFX((GtsOfx) node, eventWriter, eventFactory);
            } else if (node instanceof GtsExternalNetwork) {
                writeExternalInterface((GtsExternalNetwork) node, eventWriter, eventFactory);
            } else {
                writeHost((GtsHost) node, eventWriter, eventFactory);
            }
        }

        for (GtsLink link : model.getLinks()) {
            eventWriter.add(eventFactory.createClosureStartElement(GTS_LINK));

            eventWriter.add(eventFactory.createStringPropertyElement(GTS_PROP_ID, link.getId()));

            if (link.getCapacity() != null) {
                eventWriter.add(eventFactory.createStringPropertyElement(GTS_PROP_CAPACITY, link.getCapacity().toString()));
            }

            for (Map.Entry<String, String> extraProperty : link.getExtraProperties().entrySet()) {
                eventWriter.add(eventFactory.createStringPropertyElement(extraProperty.getKey(), extraProperty.getValue()));
            }

            for (Map.Entry<String, ? extends GtsPort> portEntry : link.getPorts().entrySet()) {
                final GtsPort port = portEntry.getValue();
                assert Objects.equals(portEntry.getKey(), port.getId());

                writePort(port, eventWriter, eventFactory);
            }


            eventWriter.add(eventFactory.createClosureEndElement()); //GTS_LINK
        }

        for (GtsPort port : model.getPorts().values()) {
            writePort(port, eventWriter, eventFactory);
        }

        for (GtsType.Adjacency adjacency : model.getAdjacencies()) {
            eventWriter.add(eventFactory.createFunctionCallElement(GTS_FUNC_ADJACENCY,
                    createClosureRefArgumentFromPortId(adjacency.getPortId1()),
                    createClosureRefArgumentFromPortId(adjacency.getPortId2())));
        }

        eventWriter.add(eventFactory.createClosureEndElement()); //name

        return sw.toString();
    }

    private ClosureRefArgument createClosureRefArgumentFromPortId(GtsAdjacency.PortId portId) {
        return portId.getParentId() != null ?
                new ClosureRefArgumentImpl(portId.getParentId(), portId.getPortId())
                : new ClosureRefArgumentImpl(portId.getPortId());
    }

    private void writePort(GtsPort port, DSLEventWriter eventWriter, DSLEventFactory eventFactory) throws DSLStreamException {

        eventWriter.add(eventFactory.createClosureStartElement(GTS_PORT));

        eventWriter.add(eventFactory.createStringPropertyElement(GTS_PROP_ID, port.getId()));

        if (port.getLineRate() != null)
            eventWriter.add(eventFactory.createStringPropertyElement(GTS_PROP_LINERATE, port.getLineRate().toString()));

        if (port.getDirectionality() != null)
            eventWriter.add(eventFactory.createStringPropertyElement(GTS_PROP_DIRECTIONALITY, port.getDirectionality()));

        for (Map.Entry<String, String> extraProperty : port.getExtraProperties().entrySet()) {
            eventWriter.add(eventFactory.createStringPropertyElement(extraProperty.getKey(), extraProperty.getValue()));
        }

        if (port.getEditorX() != -1)
            eventWriter.add(eventFactory.createNumberPropertyElement(GTS_PROP_EDITOR_X, port.getEditorX()));
        if (port.getEditorY() != -1)
            eventWriter.add(eventFactory.createNumberPropertyElement(GTS_PROP_EDITOR_Y, port.getEditorY()));

        eventWriter.add(eventFactory.createClosureEndElement()); //GTS_PORT

    }

    private void writeHost(GtsHost host, DSLEventWriter eventWriter, DSLEventFactory eventFactory) throws DSLStreamException {

        eventWriter.add(eventFactory.createClosureStartElement(GTS_HOST));
        writeNodeContents(host, eventWriter, eventFactory);

        if (host.getCpuSpeed() != null)
            eventWriter.add(eventFactory.createStringPropertyElement(GTS_PROP_CPUSPEED, host.getCpuSpeed().toString()));
        if (host.getImage() != null)
            eventWriter.add(eventFactory.createStringPropertyElement(GTS_PROP_IMAGE, host.getImage()));
        if (host.getDisk() != null)
            eventWriter.add(eventFactory.createStringPropertyElement(GTS_PROP_DISK, host.getDisk().toString()));


        writePorts(host, eventWriter, eventFactory);
        eventWriter.add(eventFactory.createClosureEndElement()); //GTS_HOST
    }

    private void writeNodeContents(GtsNode node, DSLEventWriter eventWriter, DSLEventFactory eventFactory) throws DSLStreamException {
        eventWriter.add(eventFactory.createStringPropertyElement(GTS_PROP_ID, node.getId()));

        if (node.getLocation() != null)
            eventWriter.add(eventFactory.createStringPropertyElement(GTS_PROP_LOCATION, node.getLocation()));
        if (node.getEditorX() != -1)
            eventWriter.add(eventFactory.createNumberPropertyElement(GTS_PROP_EDITOR_X, node.getEditorX()));
        if (node.getEditorY() != -1)
            eventWriter.add(eventFactory.createNumberPropertyElement(GTS_PROP_EDITOR_Y, node.getEditorY()));

        for (Map.Entry<String, String> extraProperty : node.getExtraProperties().entrySet()) {
            eventWriter.add(eventFactory.createStringPropertyElement(extraProperty.getKey(), extraProperty.getValue()));
        }

    }

    private void writeOFX(GtsOfx ofx, DSLEventWriter eventWriter, DSLEventFactory eventFactory) throws DSLStreamException {

        eventWriter.add(eventFactory.createClosureStartElement(GTS_OFX));
        writeNodeContents(ofx, eventWriter, eventFactory);

        if (ofx.getControllerIPv4() != null)
            eventWriter.add(eventFactory.createStringPropertyElement(GTS_PROP_CONTROLLER_IPV4, ofx.getControllerIPv4()));
        if (ofx.getControllerPort() != null)
            eventWriter.add(eventFactory.createStringPropertyElement(GTS_PROP_CONTROLLER_PORT, ofx.getControllerPort().toString()));

        if (ofx.getFabricIPv4() != null)
            eventWriter.add(eventFactory.createStringPropertyElement(GTS_PROP_FABRIC_IPV4, ofx.getFabricIPv4()));
        if (ofx.getFabricSubnetmaskv4() != null)
            eventWriter.add(eventFactory.createStringPropertyElement(GTS_PROP_FABRIC_SUBNETMASK_IPV4, ofx.getFabricSubnetmaskv4()));

        writePorts(ofx, eventWriter, eventFactory);
        eventWriter.add(eventFactory.createClosureEndElement()); //GTS_OFX
    }


    private void writeExternalInterface(GtsExternalNetwork externalNetwork, DSLEventWriter eventWriter, DSLEventFactory eventFactory) throws DSLStreamException {

        eventWriter.add(eventFactory.createClosureStartElement(GTS_EXTERNAL_DOMAIN));
        writeNodeContents(externalNetwork, eventWriter, eventFactory);
        writePorts(externalNetwork, eventWriter, eventFactory);
        eventWriter.add(eventFactory.createClosureEndElement()); //GTS_EXTERNAL_DOMAIN
    }

    private void writePorts(GtsNode node, DSLEventWriter eventWriter, DSLEventFactory eventFactory) throws DSLStreamException {
        for (Map.Entry<String, ? extends GtsPort> portEntry : node.getPorts().entrySet()) {
            assert Objects.equals(portEntry.getKey(), portEntry.getValue().getId());

            eventWriter.add(eventFactory.createClosureStartElement(GTS_PORT));
            eventWriter.add(eventFactory.createStringPropertyElement(GTS_PROP_ID, convertIfaceNameToPortName(portEntry.getValue().getId())));

            eventWriter.add(eventFactory.createClosureEndElement()); //GTS_PORT
        }
    }


    private String convertIfaceNameToPortName(String ifaceName) {
        while (ifaceName.indexOf(':') >= 0) {
            ifaceName = ifaceName.substring(ifaceName.indexOf(':') + 1);
        }
        return ifaceName;
    }
}
