package org.etsi.mts.tdl.openapi2tdl.next;

import java.io.File;
import java.util.Collections;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.emf.ecore.resource.Resource;
import org.etsi.mts.tdl.Annotation;
import org.etsi.mts.tdl.AnnotationType;
import org.etsi.mts.tdl.CollectionDataType;
import org.etsi.mts.tdl.DataElementMapping;
import org.etsi.mts.tdl.DataElementUse;
import org.etsi.mts.tdl.DataInstance;
import org.etsi.mts.tdl.DataResourceMapping;
import org.etsi.mts.tdl.DataType;
import org.etsi.mts.tdl.DataUse;
import org.etsi.mts.tdl.ElementImport;
import org.etsi.mts.tdl.EnumDataType;
import org.etsi.mts.tdl.LiteralValueUse;
import org.etsi.mts.tdl.Member;
import org.etsi.mts.tdl.MemberAssignment;
import org.etsi.mts.tdl.Package;
import org.etsi.mts.tdl.PackageableElement;
import org.etsi.mts.tdl.ParameterBinding;
import org.etsi.mts.tdl.ParameterMapping;
import org.etsi.mts.tdl.SimpleDataInstance;
import org.etsi.mts.tdl.SimpleDataType;
import org.etsi.mts.tdl.StructuredDataInstance;
import org.etsi.mts.tdl.StructuredDataType;
import org.etsi.mts.tdl.tdlFactory;
import org.etsi.mts.tdl.tdlPackage;
import org.etsi.mts.tdl.helper.TDLHelper;
import org.etsi.mts.tdl.transform.AbstractTranslator;
import org.openapitools.codegen.ClientOptInput;
import org.openapitools.codegen.CodegenConfig;
import org.openapitools.codegen.CodegenModel;
import org.openapitools.codegen.CodegenProperty;
import org.openapitools.codegen.DefaultGenerator;
import org.openapitools.codegen.config.CodegenConfigurator;
import org.openapitools.codegen.languages.JavaClientCodegen;
import org.openapitools.codegen.model.ModelMap;
import org.openapitools.codegen.utils.ModelUtils;
import org.osgi.service.prefs.Preferences;

import io.swagger.parser.OpenAPIParser;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.PathItem.HttpMethod;
import io.swagger.v3.oas.models.Paths;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.parser.core.models.ParseOptions;
import io.swagger.v3.parser.util.InlineModelResolver;

public class OpenAPI2TDLTranslatorNext extends AbstractTranslator {

	// TODO: cleanup and prepare for release
	private OpenAPI model;

	private boolean isXtext = false;
	private Set<String> xtextKyewords;

	private Package theTdlPackage;

	public void translate(String filename) throws Exception {
		translate(filename, sourceMappingTag, targetMappingTag);
	}

	public void translate(String filename, String sourceMappingTag, String targetMappingTag) throws Exception {
		translate(filename, sourceMappingTag, targetMappingTag, false);
	}

	public void translate(String filename, String sourceMappingTag, String targetMappingTag, boolean inline)
			throws Exception {
		sourceMappingTag = "OpenAPISpecification";
		targetMappingTag = "JavaTypesPackage";
//		Converter.setTdlTokens(Arrays.asList(new String[] {"'name'", "'type'", "'size'", "'instance'"}));
//		Converter converter = new Converter(filename);
//		converter.runConversion();
//		getGeneratedPackage().getNestedPackage().add(converter.getPackage());
		// TODO: refine according to other translators
		// TODO: separate validation into another component

		model = parseSpec(filename, inline);
		if (model.getInfo().getTitle() != null)
			getGeneratedPackage().setName(getCleanName(model.getInfo().getTitle()));
		drm = getTypeFor(sourceMappingTag, tdlPackage.Literals.DATA_RESOURCE_MAPPING);
		drm.setResourceURI(new File(filename).getName());


		theTdlPackage = TDLHelper.getTdlPackage(getTargetResource());
		if (theTdlPackage != null) {
			ElementImport importTdl = tdlFactory.eINSTANCE.createElementImport();
			importTdl.setImportedPackage(theTdlPackage);
			getGeneratedPackage().getImport().add(importTdl);
			// TODO Remove default string type
			getGeneratedPackage().getPackagedElement().remove(stringType);
		}

		IProject project = TDLHelper.getProjectForResource(getTargetResource());
		Preferences preferences = null;
		if (project != null) {
			ProjectScope ps = new ProjectScope(project);
			preferences = ps.getNode(PropertyPage.PREFERENCE_SCOPE);
		}

		Package httpPackage = TDLHelper.getHttpPackage(getTargetResource());
		Package httpMessageBasedPackage = null;
		boolean useMessageBasedApi = true;
		if (preferences != null)
			useMessageBasedApi = !preferences.getBoolean(PropertyPage.PROCEDURE_BASED, false);
		StructuredDataType messageBody = null;
		if (useMessageBasedApi) {
			if (httpPackage != null) {
				packages: for (Package p : httpPackage.getNestedPackage()) {
					for (PackageableElement e : p.getPackagedElement()) {
						if (e.eClass().equals(tdlPackage.eINSTANCE.getStructuredDataType())
								&& e.getName().equals("Body")) {
							messageBody = (StructuredDataType) e;
							httpMessageBasedPackage = p;
							break packages;
						}
					}
				}
				if (httpMessageBasedPackage != null) {
					ElementImport importHttp = tdlFactory.eINSTANCE.createElementImport();
					importHttp.setImportedPackage(httpMessageBasedPackage);
					getGeneratedPackage().getImport().add(importHttp);
				}
			}
		}

		// Java mappings
		Package mappingsPackage = null;
		Package javaPackage = null;
		AnnotationType mappingNameAnnot = null;
		boolean generateJavaMappings = true;
		if (preferences != null)
			generateJavaMappings = preferences.getBoolean(PropertyPage.GENERATE_JAVA_MAPPING, false);
		if (generateJavaMappings && theTdlPackage!=null) {
			mappingsPackage = tdlFactory.eINSTANCE.createPackage();
			mappingsPackage.setName("JavaMapping");
			getGeneratedPackage().getNestedPackage().add(mappingsPackage);
			ElementImport typesImport = tdlFactory.eINSTANCE.createElementImport();
			typesImport.setImportedPackage(getGeneratedPackage());
			mappingsPackage.getImport().add(typesImport);
			if (theTdlPackage != null) {
				ElementImport tdlImport = tdlFactory.eINSTANCE.createElementImport();
				tdlImport.setImportedPackage(theTdlPackage);
				mappingsPackage.getImport().add(tdlImport);
			}
			javaPackage = TDLHelper.getJavaPackage(getTargetResource());
			if (javaPackage != null) {
				ElementImport javaImport = tdlFactory.eINSTANCE.createElementImport();
				javaImport.setImportedPackage(javaPackage);
				mappingsPackage.getImport().add(javaImport);
			}
			drmTarget = getTypeFor(targetMappingTag, tdlPackage.Literals.DATA_RESOURCE_MAPPING);
			mappingsPackage.getPackagedElement().add(drmTarget);
			drmTarget.setResourceURI(preferences.get(PropertyPage.JAVA_API_PACKAGE, "generated.java"));
			if (javaPackage != null) {
				for (PackageableElement e : javaPackage.getPackagedElement()) {
					if (e.eClass().equals(tdlPackage.eINSTANCE.getAnnotationType())
							&& e.getName().equals("JavaPackage")) {
						Annotation annot = tdlFactory.eINSTANCE.createAnnotation();
						annot.setKey((AnnotationType) e);
						drmTarget.getAnnotation().add(annot);
						break;
					}
				}
			}
			for (PackageableElement e : theTdlPackage.getPackagedElement()) {
				if (e.eClass().equals(tdlPackage.eINSTANCE.getAnnotationType())
						&& e.getName().equals("MappingName")) {
					mappingNameAnnot = (AnnotationType) e;
					Annotation annot = tdlFactory.eINSTANCE.createAnnotation();
					annot.setKey(mappingNameAnnot);
					annot.setValue("Java");
					// XXX disabled due to serialization problem
					  drmTarget.getAnnotation().add(annot);
					break;
				}
			}
		}
		

		// Map operations to request body schemas so we can create wrappers later
		Map<String, Set<PathOperation>> schemaOperations = new Hashtable<>();
		Set<PathOperation> inlineSchemaOperations = new HashSet<>();
		Set<PathOperation> noContentOperations = new HashSet<>();
		Paths paths = model.getPaths();
		for (String pathUri: paths.keySet()) {
			PathItem path = paths.get(pathUri);
			Map<HttpMethod, Operation> ops = path.readOperationsMap();
			for (HttpMethod method : ops.keySet()) {
				Operation op = ops.get(method);
				RequestBody req = op.getRequestBody();
				if (req != null) {
					MediaType jsonContent = req.getContent().get("application/json");
					if (jsonContent != null) {
						Schema schema = jsonContent.getSchema();
						boolean array = false;
						if (getSchemaType(schema) != null && getSchemaType(schema).equals("array")) {
							schema = schema.getItems();
							array = true;
						}
						String schemaName = schema.get$ref();
						if (schemaName == null) {
							inlineSchemaOperations.add(new PathOperation(pathUri, method, op, array, schema));
						} else {
							schemaName = schemaName.replace("#/components/schemas/", "");
							Set<PathOperation> set = schemaOperations.get(schemaName);
							if (set == null) {
								set = new HashSet<>();
								schemaOperations.put(schemaName, set);
							}
							set.add(new PathOperation(pathUri, method, op, array, null));
						}
					}
				} else {
					noContentOperations.add(new PathOperation(pathUri, method, op, false, null));
				}
			}
		}
		

		StructuredDataType httpRequest = null;
		CollectionDataType httpRequestParameters = null;
		EnumDataType httpParameterLocationEnum = null;
		EnumDataType httpMethodEnum = null;
		if (httpMessageBasedPackage != null) {
			for (PackageableElement e : httpMessageBasedPackage.getPackagedElement()) {
				if (e.eClass().equals(tdlPackage.eINSTANCE.getStructuredDataType())) {
					if (e.getName().equals("Request"))
						httpRequest = (StructuredDataType) e;
				} else if (e.eClass().equals(tdlPackage.eINSTANCE.getEnumDataType())) {
					if (e.getName().equals("Method"))
						httpMethodEnum = (EnumDataType) e;
					else if (e.getName().equals("Location"))
						httpParameterLocationEnum = (EnumDataType) e;
				} else if (e.eClass().equals(tdlPackage.eINSTANCE.getCollectionDataType())) {
					if (e.getName().equals("Parameters"))
						httpRequestParameters = (CollectionDataType) e;
				}
			}
		}
		
		if (useMessageBasedApi) {
			createOperationWrapper(noContentOperations, httpRequest, httpMethodEnum, httpRequestParameters, httpParameterLocationEnum);
		}
		for (PathOperation op : inlineSchemaOperations) {
			Schema<?> schema = op.inlineSchema;
			schema.setName(op.path.substring(1) + "_body");
			DataType dataType = translate(schema, "", messageBody);
			if (useMessageBasedApi) {
				createOperationWrapper(Collections.singleton(op), httpRequest, httpMethodEnum, httpRequestParameters, httpParameterLocationEnum);
			}
		}
		for (String schemaName : model.getComponents().getSchemas().keySet()) {
			Schema<?> schema = model.getComponents().getSchemas().get(schemaName);
			schema.setName(schemaName);
			DataType dataType = translate(schema, "", messageBody);
			
			if (useMessageBasedApi) {
				Set<PathOperation> ops = schemaOperations.get(schemaName);
				if (ops != null)
					createOperationWrapper(ops, httpRequest, httpMethodEnum, httpRequestParameters, httpParameterLocationEnum);
			}
		}

		if (generateJavaMappings && mappingsPackage!=null)
			addMappings(filename, mappingsPackage, drmTarget, javaPackage, mappingNameAnnot);

		ensureUniqueNames(getGeneratedPackage());
	}
	
	private String getSchemaType(Schema<?> schema) {
		String type = schema.getType();
		if (type == null) {
			Set<String> types = schema.getTypes();
			if (types != null && !types.isEmpty())
				type = types.iterator().next();
		}
		return type;
	}
	
	private void createOperationWrapper(Set<PathOperation> ops, StructuredDataType httpRequest,
			EnumDataType httpMethodEnum, CollectionDataType httpRequestParameters,
			EnumDataType httpParameterLocationEnum) {

		for (PathOperation pathOperation : ops) {
			Operation op = pathOperation.operation;
			
			String name = op.getOperationId();
			if (name == null)
				name = pathOperation.path;
			
			StructuredDataInstance requestData = getStructuredDataInstanceFor(name);
			requestData.setDataType(httpRequest);
			
			setMemberValue(requestData, "uri", pathOperation.path);
			
			SimpleDataInstance methodValue = findContentWithName(pathOperation.method.name(), httpMethodEnum, tdlPackage.eINSTANCE.getSimpleDataInstance());
			setMemberValue(requestData, "method", methodValue);
			
			List<Parameter> opParams = op.getParameters();
			if (opParams != null && opParams.size() > 0) {
				/*
				 * Not the best approach
				DataElementUse parametersCollection = tdlFactory.eINSTANCE.createDataElementUse();
				parametersCollection.setUnassignedMember(UnassignedMemberTreatment.ANY_VALUE_OR_OMIT);
				parametersCollection.setDataElement(httpRequestParameters);
				setMemberValue(requestData, "parameters", parametersCollection);
				*/
				
				Map<String, Parameter> params = model.getComponents().getParameters();
				for (Parameter par: op.getParameters()) {
					if (par.get$ref() != null) {
						String paraName = par.get$ref();
						paraName = paraName.replace("#/components/parameters/", "");
						Parameter p = params.get(paraName);
						if (p == null)
							System.err.println("Invalid parameter reference at " + par.get$ref());
						par = p;
					}
					
					String in = par.getIn();
					if (in.equals("query") || in.equals("path")) {
						String parameterName = par.getName();
						DataType parameterType = httpRequestParameters.getItemType();
						/*
						DataElementUse parameter = tdlFactory.eINSTANCE.createDataElementUse();
						parameter.setDataElement(parameterType);
						*/
						StructuredDataInstance parameter = getStructuredDataInstanceFor(name + "__" + parameterName);
						parameter.setDataType(parameterType);
						
						SimpleDataInstance queryValue = findContentWithName(in, httpParameterLocationEnum, tdlPackage.eINSTANCE.getSimpleDataInstance());
						/*
						setParameterValue(parameter, parameterType, "location", queryValue);
						setParameterValue(parameter, parameterType, "name", par.getName());
						
						parametersCollection.getItem().add(parameter);
						*/
						setMemberValue(parameter, "location", queryValue);
						setMemberValue(parameter, "name", parameterName);
						
					} else
						System.err.println("Unsupported parameter type: " + in + " at " + par.get$ref());
				}
			}
//			Member headersMember = findContentWithName("headers", httpRequest, tdlPackage.eINSTANCE.getMember());
//			Member parametersMember = findContentWithName("parameters", httpRequest, tdlPackage.eINSTANCE.getMember());
		}
	}
	
	private void setMemberValue(StructuredDataInstance data, String memberName, Object value) {
		
		Member member = findContentWithName(memberName, data.getDataType(), tdlPackage.eINSTANCE.getMember());
		MemberAssignment assignment = tdlFactory.eINSTANCE.createMemberAssignment();
		assignment.setMember(member);
		if (value instanceof String) {
			LiteralValueUse dataUse = tdlFactory.eINSTANCE.createLiteralValueUse();
			dataUse.setValue((String) value);
			assignment.setMemberSpec(dataUse);
			
		} else if (value instanceof DataInstance) {
			DataElementUse dataUse = tdlFactory.eINSTANCE.createDataElementUse();
			dataUse.setDataElement((DataInstance) value);
			assignment.setMemberSpec(dataUse);
		} else if (value instanceof DataUse) {
			assignment.setMemberSpec((DataUse) value);
		}
		data.getMemberAssignment().add(assignment);
		
	}
	
	private void setParameterValue(DataElementUse data, DataType type, String parameterName, Object value) {
		
		org.etsi.mts.tdl.Parameter parameter = findContentWithName(parameterName, type, tdlPackage.eINSTANCE.getParameter());
		ParameterBinding binding = tdlFactory.eINSTANCE.createParameterBinding();
		binding.setParameter(parameter);
		if (value instanceof String) {
			LiteralValueUse dataUse = tdlFactory.eINSTANCE.createLiteralValueUse();
			dataUse.setValue((String) value);
			binding.setDataUse(dataUse);
			
		} else if (value instanceof DataInstance) {
			DataElementUse dataUse = tdlFactory.eINSTANCE.createDataElementUse();
			dataUse.setDataElement((DataInstance) value);
			binding.setDataUse(dataUse);
		} else if (value instanceof DataUse) {
			binding.setDataUse((DataUse) value);
		}
		data.getArgument().add(binding);
		
	}

	@Override
	public void setTargetResource(Resource targetResource) {
		super.setTargetResource(targetResource);

		Class resourceClass = targetResource.getClass();
		do {
			// Flaky
			if (resourceClass.getSimpleName().equals("XtextResource")) {
				isXtext = true;
				break;
	}
			resourceClass = resourceClass.getSuperclass();
		} while (resourceClass != null);

		if (isXtext)
			xtextKyewords = TDLHelper.getTdlGrammarKeywords();
	}

	private void addMapping(Schema<?> schema, DataType dataType, String sourceMappingTag, String targetMappingTag) {
		DataElementMapping sourceMapping = addDataElementMapping("#/components/schemas/" + schema.getName() + "",
				dataType, sourceMappingTag, drm);

		// TODO: make configurable?
		DataElementMapping targetMapping = addDataElementMapping("" + schema.getName() + "", dataType,
				targetMappingTag, drmTarget);

		if (dataType instanceof StructuredDataType) {
			for (Member m : ((StructuredDataType) dataType).getMember()) {
				addParameterMapping(sourceMapping, m, m.getName());
				addParameterMapping(targetMapping, m, m.getName());
			}
		}
	}
	
	private void getAllProperties(Schema<?> schema, Map<String, Schema> properties) {
		String reference = schema.get$ref();
		if (reference != null)
			schema = ModelUtils.getReferencedSchema(model, schema);
		List<Schema> xOf = schema.getAllOf();
		if (xOf == null)
			xOf = schema.getAnyOf();
		if (xOf == null)
			xOf = schema.getOneOf();
		if (xOf != null) {
			for (Schema ofSchema : xOf)
				this.getAllProperties(ofSchema, properties);
		}
		if (schema.getProperties() != null)
			properties.putAll(schema.getProperties());
	}

	private DataType translate(Schema<?> schema, String prefix, DataType superType) {
		Map<String, Schema> properties = new Hashtable<>();
		this.getAllProperties(schema, properties);
		if (getSchemaType(schema) == null && properties.isEmpty()) {
			if (schema.getName() == null) {
				System.out.println("Why?");
			}
			return translateSimpleType(schema, superType);
		} else {
			String name = prefix + schema.getName();
			if (schema.getName() == null) {
				name = prefix + "___item";
			}
			if (!properties.isEmpty() || getSchemaType(schema).equals("object")) {
				DataType t = translateObject(schema, name);
				addSuperType(t, superType);
				return t;
			} else if (getSchemaType(schema).equals("array")) {
				return translateArray(schema, name);
			} else {
				return translateSimpleType(schema, superType);
			}
		}
	}

	private DataType translateSimpleType(Schema<?> schema, DataType superType) {
		String name = schema.getName();
		if (name == null)
			name = getSchemaType(schema);
		

		if (this.theTdlPackage != null) {
			if (name.equals("string") || name.equals("integer") || name.equals("boolean")) {
				name = name.replaceFirst(Character.toString(name.charAt(0)),
						Character.toString(name.charAt(0)).toUpperCase());
				for (PackageableElement pe : this.theTdlPackage.getPackagedElement()) {
					if (pe.eClass().equals(tdlPackage.eINSTANCE.getSimpleDataType()) && pe.getName().equals(name))
						return (DataType) pe;
				}
			}
		}
		if (schema.getEnum() != null) {
			EnumDataType t = getEnumDataTypeFor(name);
			if (t.getValue().isEmpty())
				for (Object literal : schema.getEnum()) {
					String literalName;
					if (literal instanceof String)
						literalName = (String)literal;
					else if (literal instanceof Integer)
						literalName = name + "_" + ((Integer)literal).toString();
					else
						// TODO what if enum literals are not Strings or Integers?
						continue;

					SimpleDataInstance literalData = tdlFactory.eINSTANCE.createSimpleDataInstance();
					literalData.setName(getCleanName(literalName));
					literalData.setDataType(t);
					t.getValue().add(literalData);
				}
			return t;

		} else {
			SimpleDataType t = getSimpleDataTypeFor(name);
			addSuperType(t, superType);
			return t;
		}
	}

	private DataType translateObject(Schema<?> schema, String name) {
		Map<String, Schema> properties = new Hashtable<>();
		this.getAllProperties(schema, properties);
		StructuredDataType dataType = getStructuredDataTypeFor(name);
		if (properties.isEmpty() || !dataType.getMember().isEmpty()) {
			return dataType;
		}
		for (Object propertyName : properties.keySet()) {
			Schema<?> propertySchema = (Schema<?>) properties.get(propertyName);
			String reference = propertySchema.get$ref();
			DataType memberType = null;
			if (reference != null) {
				Schema<?> referencedSchema = ModelUtils.getReferencedSchema(model, propertySchema);
				referencedSchema.setName(ModelUtils.getSimpleRef(reference));
				memberType = translate(referencedSchema, "", null);
			} else {
				memberType = translate(propertySchema, name + "___", null);
			}
			Member m = getContentWithName((String) propertyName, dataType, tdlPackage.Literals.MEMBER);
			m.setDataType(memberType);
			if (m.container() == null) {
				dataType.getMember().add(m);
			}
		}
		return dataType;
	}

	private DataType translateArray(Schema<?> schema, String name) {
		Schema<?> itemsSchema = schema.getItems();
		String reference = itemsSchema.get$ref();
		DataType itemType;
		if (reference != null) {
			Schema<?> referencedSchema = ModelUtils.getReferencedSchema(model, itemsSchema);
			referencedSchema.setName(ModelUtils.getSimpleRef(reference));
			itemType = translate(referencedSchema, "", null);
		} else {
			itemType = translate(itemsSchema, name, null);
		}
		CollectionDataType collectionType = getTypeFor(itemType.getName() + "_collection",
				tdlPackage.Literals.COLLECTION_DATA_TYPE);
		collectionType.setItemType(itemType);
		return collectionType;
	}
	
	public String getCleanName(String name) {
		
		String replacement = name;
		if (isXtext) {
			if (replacement.matches(idStartDigitRegex))
				replacement = "_" + replacement;
			replacement = replacement.replaceAll(idInvalidCharRegex, "_");
			// This isn't needed because keywords are escaped by IDValueConverter
			/*
			 * if (xtextKyewords.contains(replacement)) { replacement = "^" + replacement; }
			 */
		}
		
		return replacement;
	}
	
	/**
	 * Helper method for parsing specs into an intermediary OpenAPI structure for
	 * pre-processing.
	 *
	 * Use this method only for tests targeting processing helpers such as
	 * {@link org.openapitools.codegen.utils.ModelUtils} or
	 * {@link InlineModelResolver}. Using this for testing generators will mean
	 * you're not testing the OpenAPI document in a state the generator will be
	 * presented at runtime.
	 *
	 * @param specFilePath The path to the specification file
	 * @return A "raw" OpenAPI document
	 * @throws Exception 
	 */
	public static OpenAPI parseSpec(String specFilePath, Boolean processInline) throws Exception {
		OpenAPI openAPI = new OpenAPIParser().readLocation(specFilePath, null, new ParseOptions()).getOpenAPI();
		
		if (openAPI == null)
			throw new Exception("Failed to parse '" + specFilePath + "', make sure it's valid ");
		// Invoke helper function to get the original swagger version.
		// See https://github.com/swagger-api/swagger-parser/pull/1374
		// Also see https://github.com/swagger-api/swagger-parser/issues/1369.
		ModelUtils.getOpenApiVersion(openAPI, specFilePath, null);

		// DONE: make optional and expose for configuration
		if (processInline) {
			InlineModelResolver inlineModelResolver = new InlineModelResolver();
			inlineModelResolver.flatten(openAPI);
		}

		return openAPI;
	}
	
	class PathOperation {
		String path;
		HttpMethod method;
		Operation operation;
		Schema<?> inlineSchema;
		boolean array;
		public PathOperation(String path, HttpMethod method, Operation operation, boolean array, Schema<?> inlineSchema) {
			this.path = path;
			this.method = method;
			this.operation = operation;
			this.inlineSchema = inlineSchema;
			this.array = array;
		}
	}

	public void addMappings(String filename, Package mappingsPackage, DataResourceMapping resourceMapping, Package javaPackage, AnnotationType mappingNameAnnot) {
		
		CodegenConfigurator configurator = new CodegenConfigurator();
		// We create our own but the setting is required by the configurator
		configurator.setGeneratorName("java");
		configurator.setInputSpec(filename.replaceAll("\\\\", "/"));
		// Set rest of configuration options directly to config (JavaClientCodegen)

		// Originally...
		// ClientOptInput clientOptInput = configurator.toClientOptInput();
		CodegenConfig config = new JavaClientCodegen() {
			@Override
			public Map<String, Object> postProcessSupportingFileData(Map<String, Object> objs) {

				AnnotationType getterAnnotation = null;
				AnnotationType setterAnnotation = null;
				AnnotationType classAnnotation = null;
				if (javaPackage != null) {
					for (PackageableElement e : javaPackage.getPackagedElement()) {
						if (e.eClass().equals(tdlPackage.eINSTANCE.getAnnotationType())) {
							if (e.getName().equals("JavaGetter"))
								getterAnnotation = (AnnotationType) e;
							if (e.getName().equals("JavaSetter"))
								setterAnnotation = (AnnotationType) e;
							if (e.getName().equals("JavaClass"))
								classAnnotation = (AnnotationType) e;
						}
					}
				}
				
				List<ModelMap> allModels = (List<ModelMap>) objs.get("models");
				for (ModelMap modelMap : allModels) {
					CodegenModel codeGen = modelMap.getModel();
					String name = codeGen.getName();
					DataType type = null;
					if (codeGen.isEnum)
						type = getEnumDataTypeFor(name);
					else
						type = getStructuredDataTypeFor(name);
					
					DataElementMapping eMapping = tdlFactory.eINSTANCE.createDataElementMapping();
					mappingsPackage.getPackagedElement().add(eMapping);
					eMapping.setName(getCleanName(name) + "_Mapping");
					eMapping.setMappableDataElement(type);
					eMapping.setElementURI(codeGen.getClassname());
					eMapping.setDataResourceMapping(resourceMapping);
					Annotation ca = tdlFactory.eINSTANCE.createAnnotation();
					ca.setKey(classAnnotation);
					eMapping.getAnnotation().add(ca);
					
					if (type instanceof EnumDataType) {
						
						DataResourceMapping eResourceMapping = tdlFactory.eINSTANCE.createDataResourceMapping();
						mappingsPackage.getPackagedElement().add(eResourceMapping);
						eResourceMapping.setName(getCleanName(name) + "_ResourceMapping");
						eResourceMapping.setResourceURI(resourceMapping.getResourceURI() + "." + codeGen.getClassname());
						ca = tdlFactory.eINSTANCE.createAnnotation();
						ca.setKey(classAnnotation);
						eResourceMapping.getAnnotation().add(ca);
						Annotation mna = tdlFactory.eINSTANCE.createAnnotation();
						mna.setKey(mappingNameAnnot);
						mna.setValue("Java");
						eResourceMapping.getAnnotation().add(mna);
						
						List<Map<String, String>> enumVars = (List<Map<String, String>>) codeGen.getAllowableValues().get("enumVars");
						for (Map<String, String> enumVar : enumVars) {
							String javaName = enumVar.get("name");
							String modelName = enumVar.get("value");
							// Wrapped in ""
							if (modelName.startsWith("\""))
								modelName = modelName.substring(1, modelName.length() - 1);
							else
								modelName = name + "_" + modelName;
							
							SimpleDataInstance literal = null;
							for (SimpleDataInstance l: ((EnumDataType) type).getValue()) {
								if (l.getName().equals(getCleanName(modelName))) {
									 literal = l;
									 break;
								}
							}
							if (literal == null)
								continue;

							DataElementMapping lMapping = tdlFactory.eINSTANCE.createDataElementMapping();
							mappingsPackage.getPackagedElement().add(lMapping);
							lMapping.setName(getCleanName(name) + "_" + getCleanName(modelName) + "_Mapping");
							lMapping.setMappableDataElement(literal);
							lMapping.setElementURI(javaName);
							lMapping.setDataResourceMapping(eResourceMapping);
						}
						
					} else  if (type instanceof StructuredDataType) {
						for (CodegenProperty var: codeGen.getAllVars()) {
							String vName = var.getBaseName();
							Member member = null;
							for (Member m: ((StructuredDataType) type).allMembers()) {
								if (m.getName().equals(getCleanName(vName))) {
									 member = m;
									 break;
								}
							}
							if (member == null)
								continue;
							
							ParameterMapping pMapping = tdlFactory.eINSTANCE.createParameterMapping();
							eMapping.getParameterMapping().add(pMapping);
							pMapping.setParameter(member);
							pMapping.setParameterURI(var.getName());

							if (getterAnnotation != null && setterAnnotation != null) {
								Annotation ga = tdlFactory.eINSTANCE.createAnnotation();
								ga.setKey(getterAnnotation);
								ga.setValue(var.getGetter());
								pMapping.getAnnotation().add(ga);
								Annotation sa = tdlFactory.eINSTANCE.createAnnotation();
								sa.setKey(setterAnnotation);
								sa.setValue(var.getSetter());
								pMapping.getAnnotation().add(sa);
							}
						}
					}
				}
				//return super.postProcessSupportingFileData(objs);
				return objs;
			}
		};
		// TODO configure if needed
		ClientOptInput input = new ClientOptInput().config(config);
		//input.userDefinedTemplates(userDefinedTemplates);
		input.openAPI((OpenAPI) configurator.toContext().getSpecDocument());

		DefaultGenerator generator = new DefaultGenerator(true);
		generator.opts(input);
		generator.generate();
	}

}
