class Splitting {

    public TTopologyTemplate split(TTopologyTemplate topologyTemplate) throws SplittingException {
        if (!checkValidTopology(topologyTemplate)) {
            throw new SplittingException("Topology is not valid");
        }

        // Copy for incremental removal of the processed nodes
        TTopologyTemplate topologyTemplateCopy = BackendUtils.clone(topologyTemplate);

        HashSet<TNodeTemplate> nodeTemplatesWhichPredecessorsHasNoPredecessors
            = new HashSet<>(getNodeTemplatesWhichPredecessorsHasNoPredecessors(topologyTemplateCopy));

        // Consider each node which hostedOn-predecessor nodes have no further hostedOn-predecessors
        while (!nodeTemplatesWhichPredecessorsHasNoPredecessors.isEmpty()) {

            for (TNodeTemplate currentNode : nodeTemplatesWhichPredecessorsHasNoPredecessors) {

                List<TNodeTemplate> predecessors = getHostedOnPredecessorsOfNodeTemplate(topologyTemplateCopy, currentNode);
                Set<String> predecessorsTargetLabel = new HashSet<>();

                for (TNodeTemplate predecessor : predecessors) {
                    Optional<String> targetLabel = ModelUtilities.getTargetLabel(predecessor);
                    if (!targetLabel.isPresent()) {
                        LOGGER.error("No target label present");
                        LOGGER.error("id " + predecessor.getId());
                    }

                    //noinspection OptionalGetWithoutIsPresent
                    predecessorsTargetLabel.add(targetLabel.get().toLowerCase());
                }
                // If all predecessors have the same target label assign this label to the considered node
                if (predecessorsTargetLabel.size() == 1) {
                    //noinspection OptionalGetWithoutIsPresent
                    ModelUtilities.setTargetLabel(currentNode, ModelUtilities.getTargetLabel(predecessors.get(0)).get());
                } else {

                    List<TRelationshipTemplate> incomingRelationships
                        = ModelUtilities.getIncomingRelationshipTemplates(topologyTemplateCopy, currentNode);
                    List<TRelationshipTemplate> outgoingRelationships
                        = ModelUtilities.getOutgoingRelationshipTemplates(topologyTemplateCopy, currentNode);

                    // Otherwise, duplicate the considered node for each target label
                    for (String targetLabel : predecessorsTargetLabel) {
                        TNodeTemplate duplicatedNode = BackendUtils.clone(currentNode);
                        duplicatedNode.setId(Util.makeNCName(currentNode.getId() + "-" + targetLabel));
                        duplicatedNode.setName(Util.makeNCName(currentNode.getName() + "-" + targetLabel));
                        topologyTemplate.getNodeTemplateOrRelationshipTemplate().add(duplicatedNode);
                        topologyTemplateCopy.getNodeTemplateOrRelationshipTemplate().add(duplicatedNode);
                        ModelUtilities.setTargetLabel(duplicatedNode, targetLabel);

                        for (TRelationshipTemplate incomingRelationship : incomingRelationships) {
                            Object sourceElementIncommingRel = incomingRelationship.getSourceElement().getRef();

                            /*
                             * incoming hostedOn relationships from predecessors with the same label and not hostedOn
                             * relationships (e.g. conntectsTo) are assigned to the duplicated node.
                             * The origin relationships are duplicated
                             */
                            TNodeTemplate sourceNodeTemplate = ModelUtilities.getSourceNodeTemplateOfRelationshipTemplate(topologyTemplateCopy, incomingRelationship);
                            if (((ModelUtilities.getTargetLabel(sourceNodeTemplate).get()
                                .equalsIgnoreCase(ModelUtilities.getTargetLabel(duplicatedNode).get())
                                && getBasisRelationshipType(incomingRelationship.getType()).getValidTarget().getTypeRef().getLocalPart().equalsIgnoreCase("Container"))
                                || !predecessors.contains(sourceNodeTemplate))) {

                                List<TRelationshipTemplate> reassignRelationship = new ArrayList<>();
                                reassignRelationship.add(incomingRelationship);
                                //Reassign incoming relationships
                                List<TRelationshipTemplate> reassignedRelationship
                                    = reassignIncomingRelationships(reassignRelationship, duplicatedNode);
                                topologyTemplate.getNodeTemplateOrRelationshipTemplate().addAll(reassignedRelationship);
                                topologyTemplateCopy.getNodeTemplateOrRelationshipTemplate().addAll(reassignedRelationship);
                            }
                        }

                        /*
                         * Reassign outgoing relationships. No difference between the relationship types.
                         * Origin outgoing relationships are duplicated and added to the duplicated node as source
                         */
                        List<TRelationshipTemplate> newOutgoingRelationships
                            = reassignOutgoingRelationships(outgoingRelationships, duplicatedNode);
                        topologyTemplate.getNodeTemplateOrRelationshipTemplate().addAll(newOutgoingRelationships);
                        topologyTemplateCopy.getNodeTemplateOrRelationshipTemplate().addAll(newOutgoingRelationships);
                    }

                    // Remove the original node and its relations from the origin topology template and the copy
                    topologyTemplate.getNodeTemplateOrRelationshipTemplate().remove(currentNode);
                    topologyTemplate.getNodeTemplateOrRelationshipTemplate().removeAll(outgoingRelationships);
                    topologyTemplate.getNodeTemplateOrRelationshipTemplate().removeAll(incomingRelationships);
                    topologyTemplateCopy.getNodeTemplateOrRelationshipTemplate().remove(currentNode);
                    topologyTemplateCopy.getNodeTemplateOrRelationshipTemplate().removeAll(outgoingRelationships);
                    topologyTemplateCopy.getNodeTemplateOrRelationshipTemplate().removeAll(incomingRelationships);
                }

                // Remove the hostedOn-predecessors of the considered node and their relations in the working copy
                topologyTemplateCopy.getNodeTemplateOrRelationshipTemplate().removeAll(predecessors);
                List<TRelationshipTemplate> removingRelationships =
                    topologyTemplateCopy.getRelationshipTemplates().stream()
                        .filter(rt -> predecessors.contains(ModelUtilities.getSourceNodeTemplateOfRelationshipTemplate(topologyTemplateCopy, rt))
                            || predecessors.contains(ModelUtilities.getTargetNodeTemplateOfRelationshipTemplate(topologyTemplateCopy, rt)))
                        .collect(Collectors.toList());

                topologyTemplateCopy.getNodeTemplateOrRelationshipTemplate().removeAll(removingRelationships);
            }
            nodeTemplatesWhichPredecessorsHasNoPredecessors.clear();
            nodeTemplatesWhichPredecessorsHasNoPredecessors.addAll(getNodeTemplatesWhichPredecessorsHasNoPredecessors(topologyTemplateCopy));
        }

        return topologyTemplate;
    }

}