class CloudFormationTemplateBuilder {

    public String build(ModelContext context) {
        Map<String, Object> model = new HashMap<>();
        Collection<AwsGroupView> awsGroupViews = new ArrayList<>();
        Collection<AwsGroupView> awsGatewayGroupViews = new ArrayList<>();
        int subnetCounter = 0;
        boolean multigw = context.stack.getGroups().stream().filter(g -> g.getType() == InstanceGroupType.GATEWAY).count() > 1;
        for (Group group : context.stack.getGroups()) {
            AwsInstanceView awsInstanceView = new AwsInstanceView(group.getReferenceInstanceTemplate());
            String encryptedAMI = context.encryptedAMIByGroupName.get(group.getName());
            AwsGroupView groupView = new AwsGroupView(
                    group.getInstancesSize(),
                    group.getType().name(),
                    awsInstanceView.getFlavor(),
                    group.getName(),
                    awsInstanceView.isEncryptedVolumes(),
                    group.getRootVolumeSize(),
                    awsInstanceView.getVolumes().stream().collect(Collectors.groupingBy(Volume::getType, Collectors.counting())),
                    group.getSecurity().getRules(),
                    group.getSecurity().getCloudSecurityIds(),
                    getSubnetIds(context.existingSubnetIds, subnetCounter, group, multigw),
                    awsInstanceView.isKmsCustom(),
                    awsInstanceView.getKmsKey(),
                    encryptedAMI,
                    group.getSecurity().isUseNetworkCidrAsSourceForDefaultRules(),
                    getInstanceProfile(group),
                    awsInstanceView.getOnDemandPercentage(),
                    awsInstanceView.isFastEbsEncryptionEnabled(),
                    awsInstanceView.getSpotMaxPrice());
            awsGroupViews.add(groupView);
            if (group.getType() == InstanceGroupType.GATEWAY) {
                awsGatewayGroupViews.add(groupView);
            }
            subnetCounter++;
        }
        model.put("instanceGroups", awsGroupViews);
        model.put("gatewayGroups", awsGatewayGroupViews);
        model.put("existingVPC", context.existingVPC);
        model.put("existingIGW", context.existingIGW);
        model.put("existingSubnet", !isNullOrEmptyList(context.existingSubnetCidr));
        model.put("enableInstanceProfile", context.enableInstanceProfile || context.instanceProfileAvailable);
        model.put("existingRole", context.instanceProfileAvailable);
        model.put("cbSubnet", (isNullOrEmptyList(context.existingSubnetCidr)) ? Lists.newArrayList(context.defaultSubnet)
                : context.existingSubnetCidr);
        model.put("vpcSubnet", context.existingVpcCidr == null ? Collections.emptyList() : context.existingVpcCidr);
        model.put("dedicatedInstances", areDedicatedInstancesRequested(context.stack));
        model.put("availabilitySetNeeded", context.ac.getCloudContext().getLocation().getAvailabilityZone() != null
                && context.ac.getCloudContext().getLocation().getAvailabilityZone().value() != null);
        model.put("mapPublicIpOnLaunch", context.mapPublicIpOnLaunch);
        model.put("outboundInternetTraffic", context.outboundInternetTraffic);
        model.put("vpcCidrs", context.vpcCidrs);
        model.put("prefixListIds", context.prefixListIds);
        try {
            String template = freeMarkerTemplateUtils.processTemplateIntoString(new Template("aws-template", context.template, freemarkerConfiguration), model);
            return template.replaceAll("\\t|\\n| [\\s]+", "");
        } catch (IOException | TemplateException e) {
            throw new CloudConnectorException("Failed to process CloudFormation freemarker template", e);
        }
    }

}