class ServiceEmitter {

    public void emitTypescriptClass() {
        Set<String> endpointsToWarnAboutDuplicateNames = Sets.newHashSet();
        if (!this.settings.emitDuplicateJavaMethodNames()) {
            endpointsToWarnAboutDuplicateNames = getDuplicateEndpointNames();
        }
        writer.writeLine("");
        // Adding "Impl" ensures the class name is different from the impl name, which is a compilation requirement.
        writer.writeLine("export class " + model.name() + "Impl" + " implements " + settings.getSettings().addTypeNamePrefix + model.name() + " {");
        writer.increaseIndent();

        writer.writeLine("");
        writer.writeLine(String.format("private httpApiBridge: %sHttpApiBridge;", settings.generatedInterfacePrefix()));
        writer.writeLine(String.format("constructor(httpApiBridge: %sHttpApiBridge) {", settings.generatedInterfacePrefix()));
        writer.increaseIndent();
        writer.writeLine("this.httpApiBridge = httpApiBridge;");
        writer.decreaseIndent();
        writer.writeLine("}");

        for (InnerServiceModel innerServiceModel : model.innerServiceModels()) {
            if (model.innerServiceModels().size() > 1) {
                writer.writeLine("");
                writer.writeLine("// endpoints for service class: " + innerServiceModel.name());
            }
            for (ServiceEndpointModel endpointModel: innerServiceModel.endpointModels()) {
                if (endpointsToWarnAboutDuplicateNames.contains(endpointModel.endpointName())) {
                    // don't output any duplicates
                    continue;
                }
                writer.writeLine("");
                String line = "public " + endpointModel.endpointName() + "(";
                line += getEndpointParametersString(endpointModel);
                line += ") {";
                writer.writeLine(line);
                writer.increaseIndent();
                writer.writeLine(String.format("var httpCallData = <%sHttpEndpointOptions> {", settings.generatedInterfacePrefix()));
                writer.increaseIndent();
                writer.writeLine("serviceIdentifier: \"" + Character.toLowerCase(model.name().charAt(0)) + model.name().substring(1) + "\",");
                writer.writeLine("endpointPath: \"" + getEndpointPathString(innerServiceModel, endpointModel) + "\",");
                writer.writeLine("endpointName: \"" + endpointModel.endpointName() + "\",");
                writer.writeLine("method: \"" + endpointModel.endpointMethodType() + "\",");
                writer.writeLine("requestMediaType: \"" + endpointModel.endpointRequestMediaType() + "\",");
                writer.writeLine("responseMediaType: \"" + optionalToString(endpointModel.endpointResponseMediaType()) + "\",");
                List<String> requiredHeaders = Lists.newArrayList();
                List<String> pathArguments = Lists.newArrayList();
                List<String> queryArguments = Lists.newArrayList();
                String dataArgument = null;
                for (ServiceEndpointParameterModel parameterModel : endpointModel.parameters()) {
                    if (parameterModel.headerParam() != null) {
                        requiredHeaders.add("\"" + parameterModel.headerParam() + "\"");
                    } else if (parameterModel.pathParam() != null) {
                        pathArguments.add(parameterModel.getParameterName());
                    } else if (parameterModel.queryParam() != null) {
                        queryArguments.add(parameterModel.queryParam());
                    } else {
                        if (dataArgument != null) {
                            throw new IllegalStateException("There should only be one data argument per endpoint. Found both" + dataArgument + " and " + parameterModel.getParameterName());
                        }
                        dataArgument = parameterModel.getParameterName();
                        boolean isEnum = false;
                        if (parameterModel.javaType() instanceof Class<?>) {
                            isEnum = ((Class<?>) parameterModel.javaType()).isEnum();
                        }
                        if (endpointModel.endpointRequestMediaType().equals(MediaType.APPLICATION_JSON) && (parameterModel.tsType().toString().equals("string") || isEnum)) {
                            // strings (and enums, the wire format of an enum is a string) have to be wrapped in quotes in order to be valid json
                            dataArgument = "`\"${" + parameterModel.getParameterName() + "}\"`";
                        }
                    }
                }
                writer.writeLine("requiredHeaders: [" + Joiner.on(", ").join(requiredHeaders) + "],");
                writer.writeLine("pathArguments: [" + Joiner.on(", ").join(pathArguments) + "],");
                writer.writeLine("queryArguments: {");
                writer.increaseIndent();
                for (String queryArgument: queryArguments) {
                    writer.writeLine(queryArgument + ": " + queryArgument + ",");
                }
                writer.decreaseIndent();
                writer.writeLine("},");
                writer.writeLine("data: " + dataArgument);
                writer.decreaseIndent();
                writer.writeLine("};");
                writer.writeLine("return this.httpApiBridge.callEndpoint<" + endpointModel.tsReturnType().toString() + ">(httpCallData);");
                writer.decreaseIndent();
                writer.writeLine("}");
            }
        }
        writer.decreaseIndent();
        writer.writeLine("}");
    }

    public  ServiceEmitter(ServiceModel model, TypescriptServiceGeneratorConfiguration settings, IndentedOutputWriter writer);

}