package org.etsi.mts.tdl.json2tdl;

import java.io.File;
import java.io.FileReader;
import java.util.Map;
import java.util.Map.Entry;

import org.etsi.mts.tdl.CollectionDataInstance;
import org.etsi.mts.tdl.CollectionDataType;
import org.etsi.mts.tdl.DataElementMapping;
import org.etsi.mts.tdl.DataElementUse;
import org.etsi.mts.tdl.DataType;
import org.etsi.mts.tdl.DataUse;
import org.etsi.mts.tdl.LiteralValueUse;
import org.etsi.mts.tdl.Member;
import org.etsi.mts.tdl.MemberAssignment;
import org.etsi.mts.tdl.ParameterBinding;
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.transform.AbstractTranslator;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;

public class JSON2TDLTranslator extends AbstractTranslator{
	private static final String JSON_STRING = "JSON_String";

    //for reference and debugging
    private void dumpJson(String prefix, Map.Entry<String,JsonElement> e) {
        System.out.println();
        System.out.print(prefix+"\t"+e.getKey() + ":");
        dumpJson(prefix + "\t", e.getValue());
    }

    //for reference and debugging
    private void dumpJson(String prefix, JsonElement e) {
        if (e.isJsonArray()) {
            ((JsonArray) e).forEach(a -> dumpJson(prefix + "\t", a));
        } else if (e.isJsonObject()) {
            ((JsonObject) e).entrySet().forEach(a -> dumpJson(prefix + "\t", a));
        } else if (e.isJsonPrimitive()) {
            System.out.print("  "+e);
        }
    }
    
    public boolean isValid(String json) {
        try {
            JsonParser.parseString(json);
        } catch (JsonSyntaxException e) {
            return false;
        }
        return true;
    }

    //inherited entry point
	@Override
	public void translate(String targetFilename) throws Exception {
		String sourceMappingTag = "SOURCE_MAPPING";
		drm = getTypeFor(sourceMappingTag, tdlPackage.Literals.DATA_RESOURCE_MAPPING);
		drm.setResourceURI(new File(targetFilename).getName());
        try {
        	//TODO: import and extend body? or how is the content to be referenced
        	//TODO: add model name as prefix for disambiguation? -> optional
            JsonElement jsonElement = JsonParser.parseReader(new FileReader(targetFilename));
            dumpJson("", jsonElement);
            System.out.println();
            
            String prefix = "JSON";
            getSimpleDataTypeFor(JSON_STRING);
            //TODO: add validation
            translate(prefix, jsonElement);
    		
            //DONE: data instances as well? -> only one data instance, 
            //TODO: make optional
            //TODO: add more sophisticated name? make configurable
            //TODO: error reporting
            translateInstance(prefix, jsonElement);
            
    		drm = getTypeFor(sourceMappingTag, tdlPackage.Literals.DATA_RESOURCE_MAPPING);
    		drm.setResourceURI(new File(targetFilename).getName());
            addMappings(prefix, jsonElement);

            //update with unique name, only after everything is in place
            //TODO: make optional / configurable
            DataType root = getDataTypeFor(prefix);            
            root.setName(prefix+"_"+cleanName(drm.getResourceURI()));
            
            //TODO: add optional extends Body? needs also corresponding import 
            //using a wrapper may be more adequate 
            
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
	}

    //translate elements
    private void translate(String prefix, JsonElement e) {
        if (e.isJsonArray()) {
            String iPrefix = prefix+"_item";
            CollectionDataType collectionType = getTypeFor(prefix, tdlPackage.Literals.COLLECTION_DATA_TYPE);
            ((JsonArray) e).forEach(a -> translate(iPrefix, a));
    		DataType itemType = getDataTypeFor(JSON_STRING);
    		if (!e.getAsJsonArray().isEmpty()) {
    			if (e.getAsJsonArray().get(0).isJsonPrimitive()) {
    				
    			} else {
    				itemType = getDataTypeFor(iPrefix);
    			}
    		}
			collectionType.setItemType(itemType);
        } else if (e.isJsonObject()) {
            StructuredDataType dataType = getStructuredDataTypeFor(prefix);
            ((JsonObject) e).entrySet().forEach(a -> translate(prefix, a));
        } else if (e.isJsonPrimitive()) {
        	//TODO: needs handling?
//            System.out.print("  "+e);
        }
    }

    //translate assignments
    private void translate(String prefix, Map.Entry<String,JsonElement> e) {
    	//TODO: extract prefixing scheme / method
        String iPrefix =  "JSON_"+e.getKey(); //short prefix
        
        //TODO: make configurable: default: last segment only -> explain possible issues
        //TODO: also for YAML/ASN.1 imports
        //TODO: can we make name, type, component not be restricted names?
        if (fullPrefix) {
        	iPrefix = 	prefix+"_"+e.getKey(); //full prefix
        }
        translate(iPrefix, e.getValue());

    	StructuredDataType dataType = getStructuredDataTypeFor(prefix);
    	//TODO: deduplicate types?
    	//TODO: handle booleans and integers?
    	//TODO: add mappings to reconstruct JSON
    	//DONE: permit "-" and ":" in names? -> "-" feasible / enabled, ":" more challenging due to wide use, 
    	//note also mapping to target which will require substitution in any case
    	//TODO: make substitution configurable
    	Member m = getContentWithName(e.getKey(), dataType, tdlPackage.Literals.MEMBER);
    	DataType memberType = getSimpleDataTypeFor(JSON_STRING);
    	if (!e.getValue().isJsonPrimitive()) {
    		memberType = getDataTypeFor(iPrefix);
    	}
    	m.setDataType(memberType);
    	dataType.getMember().add(m);
    }
    
    //translate elements
    private void addMappings(String prefix, JsonElement e) {
        if (e.isJsonArray()) {
            String iPrefix = prefix+"_item";
            CollectionDataType collectionType = getTypeFor(prefix, tdlPackage.Literals.COLLECTION_DATA_TYPE);
            ((JsonArray) e).forEach(a -> addMappings(iPrefix, a));
            //TODO: what about the rest?
        } else if (e.isJsonObject()) {
            StructuredDataType dataType = getStructuredDataTypeFor(prefix);
            //TODO: make replacement optional, more robust
    		DataElementMapping addDataElementMapping = addDataElementMapping(prefix.replaceAll("_", "."), dataType, sourceMappingTag, drm);
            ((JsonObject) e).entrySet().forEach(a -> addMappings(prefix, a));
        } else if (e.isJsonPrimitive()) {
        	//TODO: needs handling?
//            System.out.print("  "+e);
        }
    }

    //translate assignments
    private void addMappings(String prefix, Map.Entry<String,JsonElement> e) {
    	//TODO: extract prefixing scheme / method
        String iPrefix =  "JSON_"+e.getKey(); //short prefix
        
        if (fullPrefix) {
        	iPrefix = 	prefix+"_"+e.getKey(); //full prefix
        }

    	StructuredDataType dataType = getStructuredDataTypeFor(prefix);
    	Member m = getContentWithName(e.getKey(), dataType, tdlPackage.Literals.MEMBER);

    	DataElementMapping sourceMapping = getTypeFor(prefix+"_"+sourceMappingTag, tdlPackage.Literals.DATA_ELEMENT_MAPPING);
    	addParameterMapping(sourceMapping, m, e.getKey());
    	//TODO: fully qualified uris?
    	
    	addMappings(iPrefix, e.getValue());
    }
   
	
	//first level: data instance 
	private void translateInstance(String prefix, JsonElement e) {
		if (e.isJsonArray()) {
			String iPrefix = prefix + "_item";
    		CollectionDataType collectionType = getTypeFor(prefix, tdlPackage.Literals.COLLECTION_DATA_TYPE);
    		CollectionDataInstance cInstance = getTypeFor(prefix+"_instance", tdlPackage.Literals.COLLECTION_DATA_INSTANCE);
    		cInstance.setDataType(collectionType);
            ((JsonArray) e).forEach(a -> cInstance.getItem().add(translateDataUse(iPrefix, a)));
		} else if (e.isJsonObject()) {
            StructuredDataType dataType = getStructuredDataTypeFor(prefix);
			StructuredDataInstance eInstance = getTypeFor(prefix+"_instance", tdlPackage.Literals.STRUCTURED_DATA_INSTANCE);
			eInstance.setDataType(dataType);
            ((JsonObject) e).entrySet().forEach(a->eInstance.getMemberAssignment().add(translateMemberAssignment(prefix, a)));
		} else if (e.isJsonPrimitive()) {
			System.out.print("  " + e);
    		//TODO: complete?
		}
	}
	
	//TODO: move to parent? -> too specific, abstract away..
	//first level: data instance member assignments
    private MemberAssignment translateMemberAssignment(String prefix, Map.Entry<String,JsonElement> e) {
        StructuredDataType dataType = getStructuredDataTypeFor(prefix);
    	Member m = getContentWithName(e.getKey(), dataType, tdlPackage.Literals.MEMBER);

    	DataUse du = translateDataUse(m.getDataType().getName(), e.getValue());
    	
    	MemberAssignment ma = (MemberAssignment) tdlFactory.eINSTANCE.create(tdlPackage.Literals.MEMBER_ASSIGNMENT);
		ma.setMember(m);
		ma.setMemberSpec(du);
		return ma;
    }

    //nested levels: data use
	private DataUse translateDataUse(String prefix, JsonElement e) {
		if (e.isJsonArray()) {
			String iPrefix = prefix + "_item";
			DataElementUse du = (DataElementUse) tdlFactory.eINSTANCE.create(tdlPackage.Literals.DATA_ELEMENT_USE);
            ((JsonArray) e).forEach(a -> du.getItem().add(translateDataUse(iPrefix, a)));
			return du;
		} else if (e.isJsonObject()) {
			DataElementUse du = (DataElementUse) tdlFactory.eINSTANCE.create(tdlPackage.Literals.DATA_ELEMENT_USE);
            ((JsonObject) e).entrySet().forEach(a-> du.getArgument().add(translateParameterBindings(prefix, a)));
            return du;
		} else if (e.isJsonPrimitive()) {
    		LiteralValueUse lvu = (LiteralValueUse) tdlFactory.eINSTANCE.create(tdlPackage.Literals.LITERAL_VALUE_USE);
    		lvu.setValue(e.getAsString());;
    		return lvu;
		} else {
			//TODO: what should happen here?
			return null;
		}
	}

    //nested levels: parameter bindings
	private ParameterBinding translateParameterBindings(String prefix, Entry<String, JsonElement> a) {
        StructuredDataType dataType = getStructuredDataTypeFor(prefix);
		Member m = getContentWithName(a.getKey(), dataType, tdlPackage.Literals.MEMBER);
		ParameterBinding pb = (ParameterBinding) tdlFactory.eINSTANCE.create(tdlPackage.Literals.PARAMETER_BINDING);
		pb.setParameter(m);
		pb.setDataUse(translateDataUse(m.getDataType().getName(), a.getValue()));
		return pb;
	}

}
