package org.etsi.mts.tdl.json2tdl;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.util.Files;
import org.etsi.mts.tdl.CollectionDataType;
import org.etsi.mts.tdl.DataElementMapping;
import org.etsi.mts.tdl.DataElementUse;
import org.etsi.mts.tdl.DataUse;
import org.etsi.mts.tdl.LiteralValueUse;
import org.etsi.mts.tdl.MappableDataElement;
import org.etsi.mts.tdl.MemberAssignment;
import org.etsi.mts.tdl.NamedElement;
import org.etsi.mts.tdl.Package;
import org.etsi.mts.tdl.Parameter;
import org.etsi.mts.tdl.ParameterBinding;
import org.etsi.mts.tdl.SimpleDataType;
import org.etsi.mts.tdl.StructuredDataInstance;
import org.etsi.mts.tdl.StructuredDataType;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class TDL2JSONTranslator  {

	private Map<MappableDataElement, DataElementMapping> dataMappings = new LinkedHashMap<>();
	private Map<Parameter, String> parameterMappings = new LinkedHashMap<>();

	public void transform(Package p) {
		indexMappings(p);
		List<StructuredDataInstance> instances = EcoreUtil2.getAllContentsOfType(p, StructuredDataInstance.class);
		instances.forEach(this::translate);
	}

	public void indexMappings(Package p) {
		//TODO: assuming mappings are in the same package
		//TODO: differentiate mappings
		dataMappings = EcoreUtil2.getAllContentsOfType(p, DataElementMapping.class).stream()
				.filter(e->e.getName().endsWith("SOURCE_MAPPING"))
				.collect(Collectors.toMap(e->e.getMappableDataElement(), e->e));
		dataMappings.values().forEach(this::indexParameterMappings);
	}
	
	private void indexParameterMappings(DataElementMapping dem) {
		dem.getParameterMapping().forEach(pm -> parameterMappings.put(pm.getParameter(), pm.getParameterURI()));
	}
	
    public String translate(StructuredDataInstance instance) {
        Gson gson = new GsonBuilder().setPrettyPrinting().create();

        //DONE: test out basic individual data use translation
        //instance.getMemberAssignment().forEach(ma -> translate(ma.getMemberSpec()));
        
        LinkedHashMap<String, Object> map = new LinkedHashMap<>();
        instance.getMemberAssignment().forEach(ma -> transform(ma, map));
        String json = gson.toJson(map);
        
        //TODO: other primitives not serialised?
        //TODO: refine and extract
        System.out.println(json);
        String prefix = "examples/json/";
        String filename = prefix + instance.getQualifiedName().replaceAll("::", "_") + ".json";
		Files.writeStringIntoFile(filename , json);
        return json;
    }

    public String translate(DataUse du) {
        Gson gson = new GsonBuilder().setPrettyPrinting().create();
        String json = gson.toJson(transform(du));
//        System.out.println(json);
        return json;
    }
    
	public Object transform(DataUse du) {
		if (du.resolveDataType() instanceof StructuredDataType) {
			//TODO: should it apply for function calls and others?
			LinkedHashMap<String, Object> map = new LinkedHashMap<>();
			du.getArgument().forEach(pb -> transform(pb, map));
			
			//TODO: handle data element if assigned -> structured instance supported, others?
			//TODO: also others?
			//TODO: handle reduction?
			if (du instanceof DataElementUse) {
				NamedElement de = ((DataElementUse) du).getDataElement();
				if (de instanceof StructuredDataInstance) {
					((StructuredDataInstance) de).getMemberAssignment().stream()
						.filter(e->du.getArgument().stream()
								.noneMatch(a ->a.getParameter() == e.getMember()))
						.forEach(ma -> transform(ma, map));
				}
			}
			
			return map;
		} else if (du.resolveDataType() instanceof CollectionDataType) {
			if (du instanceof DataElementUse) {
				return ((DataElementUse) du).getItem().stream()
						.map(this::transform)
						.collect(Collectors.toList());
			} else {
				return null;
			}
		} else if (du.resolveDataType() instanceof SimpleDataType) {
			if (du instanceof LiteralValueUse) {
				//TODO: also intValue and boolValue
				//TODO: also differentiate during JSON2TDL 
				LiteralValueUse lvu = ((LiteralValueUse) du);
				if (lvu.getIntValue()!=null) {
					return lvu.getIntValue();
				} else if (lvu.getBoolValue()!=null) {
					return lvu.getBoolValue();
				}
				return lvu.getValue();
			} else {
				//TODO: handle other data uses
				return null;
			}
		} else {
			//TODO: handle other data uses
			return null;
		}

	}

	private void transform(ParameterBinding pb, LinkedHashMap<String, Object> map) {
		String key = pb.getParameter().getName();
		//TODO: handle extensions?
		String mappedKey = parameterMappings.get(pb.getParameter());
		if (mappedKey != null) {
			key = mappedKey;
		};
		map.put(key, transform(pb.getDataUse()));
	}
	
	private void transform(MemberAssignment ma, LinkedHashMap<String, Object> map) {
		String key = ma.getMember().getName();
		//TODO: handle extensions?
		String mappedKey = parameterMappings.get(ma.getMember());
		if (mappedKey != null) {
			key = mappedKey;
		};
		map.put(key, transform(ma.getMemberSpec()));
	}


}
