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

import java.io.File;

import org.etsi.mts.tdl.CollectionDataType;
import org.etsi.mts.tdl.DataElementMapping;
import org.etsi.mts.tdl.DataType;
import org.etsi.mts.tdl.Member;
import org.etsi.mts.tdl.StructuredDataType;
import org.etsi.mts.tdl.tdlPackage;
import org.etsi.mts.tdl.transform.AbstractTranslator;
import org.openapitools.codegen.utils.ModelUtils;

import io.swagger.parser.OpenAPIParser;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.Schema;
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;
	
	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 {
//		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);
		drm = getTypeFor(sourceMappingTag, tdlPackage.Literals.DATA_RESOURCE_MAPPING);
		drm.setResourceURI(new File(filename).getName());
		drmTarget = getTypeFor(targetMappingTag, tdlPackage.Literals.DATA_RESOURCE_MAPPING);
		//TODO: make configurable
		drmTarget.setResourceURI("generated/java");
		for (String schemaName: model.getComponents().getSchemas().keySet()) {
			Schema<?> schema = model.getComponents().getSchemas().get(schemaName);
			schema.setName(schemaName);
			DataType dataType = translate(schema, "");
			addMapping(schema, dataType, sourceMappingTag, targetMappingTag);
		}
		
	}

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

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

		if (dataType instanceof StructuredDataType) {
			for (Member m : ((StructuredDataType) dataType).getMember()) {
				addParameterMapping(sourceMapping, m, m.getName());
				addParameterMapping(targetMapping, m, m.getName());
			}
		}
	}

	private DataType translate(Schema<?> schema, String prefix) {
		if (schema.getType()==null && (schema.getProperties() == null || schema.getProperties().isEmpty())) {
			if (schema.getName() == null) {
				System.out.println("Why?");
			}
			return getSimpleDataTypeFor(schema.getName());
		} else {
			String name = prefix+schema.getName();
			if (schema.getName() == null) {
				name = prefix+"___item";
			}
			if (schema.getProperties() !=null && !schema.getProperties().isEmpty() || 
					schema.getType().equals("object")) {
				return translateObject(schema, name);
			} else if (schema.getType().equals("array")) {
				return translateArray(schema, name);
			} else {
				return getSimpleDataTypeFor(schema.getType());
			}
		}
	}

	private DataType translateObject(Schema<?> schema, String name) {
		StructuredDataType dataType = getStructuredDataTypeFor(name);
		if (schema.getProperties()==null) {
			return dataType;
		}
		for (Object propertyName : schema.getProperties().keySet()) {
			Schema<?> propertySchema = (Schema<?>) schema.getProperties().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, "");
			} else {
				memberType = translate(propertySchema, name+"___");
			}
			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();
		CollectionDataType collectionType = getTypeFor(name, tdlPackage.Literals.COLLECTION_DATA_TYPE);
		String reference = itemsSchema.get$ref();
		if (reference!=null) {
			Schema<?> referencedSchema = ModelUtils.getReferencedSchema(model, itemsSchema);
			DataType itemType = translate(referencedSchema, name);
			collectionType.setItemType(itemType);
		} else {
			DataType itemType = translate(itemsSchema, name);
			collectionType.setItemType(itemType);
		}
		return collectionType;
	}

	
	/**
     * 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
     */
    public static OpenAPI parseSpec(String specFilePath, Boolean processInline) {
        OpenAPI openAPI = new OpenAPIParser().readLocation(specFilePath, null, new ParseOptions()).getOpenAPI();
        // 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;
    }

}
