package org.etsi.mts.tdl.yang2tdl;

import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

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.Constraint;
import org.etsi.mts.tdl.DataElementMapping;
import org.etsi.mts.tdl.DataResourceMapping;
import org.etsi.mts.tdl.DataType;
import org.etsi.mts.tdl.ElementImport;
import org.etsi.mts.tdl.EnumDataType;
import org.etsi.mts.tdl.Extension;
import org.etsi.mts.tdl.LiteralValueUse;
import org.etsi.mts.tdl.Member;
import org.etsi.mts.tdl.Package;
import org.etsi.mts.tdl.PackageableElement;
import org.etsi.mts.tdl.SimpleDataInstance;
import org.etsi.mts.tdl.SimpleDataType;
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.opendaylight.yangtools.odlext.parser.MountStatementSupport;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.QNameModule;
import org.opendaylight.yangtools.yang.common.Revision;
import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
import org.opendaylight.yangtools.yang.model.api.ContainerLike;
import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.opendaylight.yangtools.yang.model.api.SchemaNode;
import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.stmt.LeafStatement;
import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition.EnumPair;
import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
import org.opendaylight.yangtools.yang.model.ri.stmt.impl.eff.DeclaredCaseEffectiveStatement;
import org.opendaylight.yangtools.yang.model.ri.stmt.impl.eff.EmptyChoiceEffectiveStatement;
import org.opendaylight.yangtools.yang.model.ri.stmt.impl.eff.EmptyLeafEffectiveStatement;
import org.opendaylight.yangtools.yang.model.ri.stmt.impl.eff.EmptyLeafListEffectiveStatement;
import org.opendaylight.yangtools.yang.model.ri.stmt.impl.eff.RegularChoiceEffectiveStatement;
import org.opendaylight.yangtools.yang.model.ri.stmt.impl.eff.RegularLeafEffectiveStatement;
import org.opendaylight.yangtools.yang.model.ri.stmt.impl.eff.RegularLeafListEffectiveStatement;
import org.opendaylight.yangtools.yang.model.ri.stmt.impl.eff.SlimLeafListEffectiveStatement;
import org.opendaylight.yangtools.yang.model.ri.stmt.impl.eff.UndeclaredCaseEffectiveStatement;
import org.opendaylight.yangtools.yang.model.spi.meta.AbstractDeclaredEffectiveStatement.Default;
import org.opendaylight.yangtools.yang.model.spi.source.FileYangTextSource;
import org.opendaylight.yangtools.yang.parser.api.YangParserConfiguration;
import org.opendaylight.yangtools.yang.parser.api.YangSyntaxErrorException;
import org.opendaylight.yangtools.yang.parser.rfc7950.reactor.RFC7950Reactors;
import org.opendaylight.yangtools.yang.parser.rfc7950.repo.YangStatementStreamSource;
import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase;
import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
import org.opendaylight.yangtools.yang.parser.stmt.reactor.CrossSourceStatementReactor;
import org.opendaylight.yangtools.yang.parser.stmt.reactor.CrossSourceStatementReactor.BuildAction;
import org.osgi.service.prefs.Preferences;

public class Yang2TDLTranslator extends AbstractTranslator {

    private static final String REFERENCE_DESCRIPTION = "ReferenceDescription";
	private static final String LIST_KEY = "ListKey";
	private static final String LEAF_REF_PATH = "LeafRefPath";

	private boolean isXtext = false;

	private Package theTdlPackage;
	private StructuredDataType messageBody = null;

	private String limit = null;
	private AnnotationType mappingNameAnnotation;

	public void setLimit(String limit) {
		this.limit = limit;
	}

	@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);
	}

	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;
	}

	//inherited entry point
	@Override
	public void translate(String targetFilename) throws Exception {
//		this.sourceMappingTag = "YangSpecification";
		this.targetMappingTag = "YangSpecification";
		
		drm = getTypeFor(sourceMappingTag, tdlPackage.Literals.DATA_RESOURCE_MAPPING);
		drm.setResourceURI(new File(targetFilename).getName());
		

		IProject project = TDLHelper.getProjectForResource(getTargetResource());
		Preferences preferences = null;
		if (project != null) {
			ProjectScope ps = new ProjectScope(project);
			preferences = ps.getNode(PropertyPage.PREFERENCE_SCOPE);
		}
		
		theTdlPackage = TDLHelper.getTdlPackage(getTargetResource());
		mappingNameAnnotation = null;
		if (theTdlPackage != null) {
			ElementImport importTdl = tdlFactory.eINSTANCE.createElementImport();
			importTdl.setImportedPackage(theTdlPackage);
			getGeneratedPackage().getImport().add(importTdl);
			// TODO Remove default string type
			getGeneratedPackage().getPackagedElement().remove(stringType);
			

			for (PackageableElement e : theTdlPackage.getPackagedElement()) {
				if (e.eClass().equals(tdlPackage.eINSTANCE.getAnnotationType())
						&& e.getName().equals("MappingName")) {
					mappingNameAnnotation = (AnnotationType) e;
					break;
				}
			}
		}

		Package httpPackage = TDLHelper.getHttpPackage(getTargetResource());
		Package httpMessageBasedPackage = null;
//		boolean useMessageBasedApi = true;
//		if (preferences != null)
//			useMessageBasedApi = !preferences.getBoolean(PropertyPage.PROCEDURE_BASED, false);
//		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")) {
						this.messageBody = (StructuredDataType) e;
						httpMessageBasedPackage = p;
						break packages;
					}
				}
			}
			if (httpMessageBasedPackage != null) {
				ElementImport importHttp = tdlFactory.eINSTANCE.createElementImport();
				importHttp.setImportedPackage(httpMessageBasedPackage);
				getGeneratedPackage().getImport().add(importHttp);
			}
		}

        CrossSourceStatementReactor reactor = createReactor();
		BuildAction build = reactor.newBuild();

		//TODO: extract
		//DONE: resolve files -> load everything from the root folder (naive approach)
		//TODO: handle folders as well?
		//TODO: handle duplicates modules?
		System.out.println("Load YANG libraries from parent:");
		Path target = Path.of(targetFilename);
		Path parent = target;
		while (parent.getParent() != null && !parent.toAbsolutePath().toString().equals(limit)) {
			parent = parent.getParent();
		}
		if (parent != target) {
			Files.walk(parent).filter(f->
					f.toFile().isFile() &&
					f.toString().endsWith(".yang") &&
					!f.toString().equals(targetFilename)
				).forEach(f -> {
					System.out.println(f);
					build.addLibSource(moduleFromPath(f.toString()));
				}
			);
//		Files.find(parent, 20, (f, p) -> 
//			p.isRegularFile() && 
//			f.toString().endsWith(".yang") &&
//			!f.toString().equals(targetFilename)
//		).forEach(f -> {
//				System.out.println(f);
//				build.addLibSource(moduleFromPath(f.toString()));
//			}
//		);
		}

		//TODO: provide some base libraries by default?
//		Files.list(Path.of("samples", "ietf"))
//			.filter(p->p.toString().endsWith("yang"))
//			.forEach(p -> 
//			build.addLibSource(moduleFromPath(p.toString()))
//		);
		System.out.println("Processing: "+target);
		if (target.toFile().isDirectory()) {
			Files.walk(target).filter(f->
					f.toFile().isFile() &&
					f.toString().endsWith(".yang") &&
					!f.toString().equals(targetFilename)
					).forEach(f -> {
						System.out.println(f);
						build.addSource(moduleFromPath(f.toString()));
					}
				);
			//non-recursive version
//			Files.list(target)
//				.filter(p->p.toString().endsWith("yang"))
//				.forEach(p -> 
//				build.addSource(moduleFromPath(p.toString()))
//			);
		} else {
			build.addSource(moduleFromPath(targetFilename));
		}
		SchemaContext schemaContext = build.buildEffective();
		//TODO: handle multimodule paths? -> can handle folders recursively
		//TODO: include meta-data -> need to sort out multipe annotations
		//TODO: library of built-in types
		//TODO: add extensions
		drmTarget = getTypeFor(targetMappingTag, tdlPackage.Literals.DATA_RESOURCE_MAPPING);

		if (this.mappingNameAnnotation != null) {
			Annotation annot = tdlFactory.eINSTANCE.createAnnotation();
			annot.setKey(this.mappingNameAnnotation);
			annot.setValue("Yang");
			drmTarget.getAnnotation().add(annot);
		}
//		mappingsPackage.getPackagedElement().add(drmTarget);
		drmTarget.setResourceURI("TODO");
		translate(schemaContext);

		ensureUniqueNames(getGeneratedPackage());
	}

	private void translate(SchemaContext schemaContext) {
		for (DataSchemaNode c : schemaContext.getChildNodes()) {
			translate(c);
			//TODO: process types as well?
		}
	}

	private DataType translate(DataSchemaNode c) {
		return translate(c, null);
	}
	
	private DataType translate(DataSchemaNode c, String parent) {
		//wrapper for meta-data
		//TODO: make optional
		//TODO: resolve issues with annotations
		//TODO: use notes?
		DataType dataType = translateOnly(c, parent);
//		c.getDescription().ifPresent(e-> {
//			annotateWith(dataType, REFERENCE_DESCRIPTION, e);
//		});
		return dataType;
	}
	
	private DataType translateOnly(DataSchemaNode c, String parent) {
		String baseName = getCleanName(c.getQName().getLocalName());
		String name = baseName;
		if (parent != null) {
			name = parent+"___"+name;
		}
		System.out.println(name);
		if (c instanceof ContainerSchemaNode) {
			//DONE: remove wrapper container structured data type of only a collection is contained
			Collection<? extends DataSchemaNode> childNodes = ((ContainerSchemaNode) c).getChildNodes();
			if (childNodes.isEmpty()) {
				//create structured data type
				StructuredDataType dataType = translateStructuredDataType((ContainerSchemaNode) c, baseName, name);
				return dataType;
			}
			DataSchemaNode first = childNodes.iterator().next();
			if (childNodes.size() == 1 && first instanceof ListSchemaNode) {
				//create collection
				return translate(first, parent); //parent?
			} else {
				//create structured data type
				StructuredDataType dataType = translateStructuredDataType((ContainerSchemaNode) c, baseName, name);
				return dataType;
			}
		} else if (c instanceof ListSchemaNode) {
			StructuredDataType itemType = translateStructuredDataType((ListSchemaNode) c, baseName, name);
			CollectionDataType collectionType = getCollectionDataTypeFor(itemType);
			addMapping(c, collectionType, sourceMappingTag, targetMappingTag);

			//TODO: add key annotation?
			List<QName> keyDefinition = ((ListSchemaNode) c).getKeyDefinition();
			String key = keyDefinition.stream()
					.map(QName::getLocalName)
					.collect(Collectors.joining(", "));
			// TODO: make optional
//			annotateWith(collectionType, LIST_KEY, key);
			
			//TODO: add max count constraint
			((ListSchemaNode) c).getElementCountConstraint().ifPresent(cs -> {
				//TODO: extract
				Integer value = cs.getMaxElements();
				String constraintName = "maxLength";
				if (value == null) {
					value = cs.getMinElements();
					constraintName = "minLength";
				}
				Constraint constraint = constrainWith(collectionType, constraintName);
				LiteralValueUse ut = tdlFactory.eINSTANCE.createLiteralValueUse();
				ut.setIntValue(BigInteger.valueOf(value.longValue()));
				constraint.getQuantifier().add(ut);
			});
			
			return collectionType;
		} else if (c instanceof RegularLeafListEffectiveStatement) {
			//TODO: what is the difference?
			TypeDefinition<?> type = ((RegularLeafListEffectiveStatement) c).getType();
			CollectionDataType collectionType = translateSimpleDataCollection(name, type);
			return collectionType;
		} else if (c instanceof EmptyLeafListEffectiveStatement) {
			//DONE: unify and reuse
			TypeDefinition<?> type = ((EmptyLeafListEffectiveStatement) c).getType();
			CollectionDataType collectionType = translateSimpleDataCollection(name, type);
			return collectionType;
		} else if (c instanceof SlimLeafListEffectiveStatement) {
			//TODO: check and remove?
			TypeDefinition<?> type = ((SlimLeafListEffectiveStatement) c).getType();
			CollectionDataType collectionType = translateSimpleDataCollection(name, type);
			return collectionType;
		} else if (c instanceof EmptyLeafEffectiveStatement) {
			TypeDefinition<?> type = ((EmptyLeafEffectiveStatement) c).getType();
			SimpleDataType dataType = translateSimpleDataType(name, type);
			return dataType;
		} else if (c instanceof RegularLeafEffectiveStatement) {
			//TODO: what is the difference? is it used
			String typeName = ((RegularLeafEffectiveStatement) c).getType().getQName().getLocalName();
			SimpleDataType dataType = getSimpleDataTypeFor(typeName);
			addMapping(c, dataType, sourceMappingTag, targetMappingTag);
			return dataType;
		} else if (c instanceof RegularChoiceEffectiveStatement) {
			//TODO: difference?
			StructuredDataType dataType = getStructuredDataTypeFor(name);
			for (CaseSchemaNode child : ((RegularChoiceEffectiveStatement) c).getCases()) {
				translateMember(baseName, dataType, child);
			}
			addMapping(c, dataType, sourceMappingTag, targetMappingTag);
			return dataType;
		} else if (c instanceof EmptyChoiceEffectiveStatement) {
			//TODO: add annotation choice
			//TODO: need to skip cases?
			//TODO: structured or simple?!
			StructuredDataType dataType = getStructuredDataTypeFor(name);
			for (CaseSchemaNode child : ((EmptyChoiceEffectiveStatement) c).getCases()) {
				translateMember(baseName, dataType, child);
			}
			addMapping(c, dataType, sourceMappingTag, targetMappingTag);
			return dataType;
		} else if (c instanceof UndeclaredCaseEffectiveStatement) {
			//choice -> container? (no case)
			return translateStructuredDataType((DataNodeContainer) c, baseName, name);
		} else if (c instanceof DeclaredCaseEffectiveStatement) {
			//choice -> case
			//TODO: compare and check
			return translateStructuredDataType((DataNodeContainer) c, baseName, name);
		} else {
			System.out.println("TODO:: "+c);
			SimpleDataType todoType = getSimpleDataTypeFor("TODO");
			return todoType;
		}
	}

	private CollectionDataType translateSimpleDataCollection(String name, TypeDefinition<?> type) {
		SimpleDataType itemType = translateSimpleDataType(name, type);
		CollectionDataType collectionType = getCollectionDataTypeFor(itemType);
		addMapping(type, collectionType, sourceMappingTag, targetMappingTag);
		return collectionType;
	}

	private SimpleDataType translateSimpleDataType(String name, TypeDefinition<?> type) {
		if (type instanceof IdentityrefTypeDefinition) {
			//TODO: handle further?
//			System.out.println("identityref:"+name+" -> "+((IdentityrefTypeDefinition)type).getIdentities());
		}
		if (type instanceof LeafrefTypeDefinition) {
			//TODO: handle further?
//			System.out.println("leafref:"+name+" -> "+((LeafrefTypeDefinition)type).getPathStatement().getOriginalString());
		}

		TypeDefinition<?> baseType = type.getBaseType();
		String typeName = type.getQName().getLocalName();
//		typeName = name; //DONE: fine? -> seems to be -> causes lots of redundant types
		if (baseType instanceof EnumTypeDefinition) { 
			//DONE: handle enumeration -> add instances rather than extension
			EnumDataType dataType = getEnumDataTypeFor(typeName);
			for (EnumPair e : ((EnumTypeDefinition)baseType).getValues()) {
				SimpleDataInstance eInstance = getEnumDataInstanceFor(e.getName(), dataType);
				eInstance.setDataType(dataType);
				dataType.getValue().add(eInstance);
			}
			addMapping(baseType, dataType, sourceMappingTag, targetMappingTag);

			//TODO: add default?
			return dataType;
			//TODO: union of simple data types? also enums?
			//TODO: adapt further?
		} else if (type instanceof EnumTypeDefinition) {
			EnumDataType dataType = getEnumDataTypeFor(name);
			for (EnumPair e : ((EnumTypeDefinition)type).getValues()) {
				SimpleDataInstance eInstance = getEnumDataInstanceFor(e.getName(), dataType);
				eInstance.setDataType(dataType);
				dataType.getValue().add(eInstance);
			}
			addMapping(type, dataType, sourceMappingTag, targetMappingTag);

			return dataType;
		} else if (type instanceof UnionTypeDefinition) {
			SimpleDataType dataType = getSimpleDataTypeFor(name);
			//dataType.setName(type.getQName().getLocalName());
			List<TypeDefinition<?>> types = ((UnionTypeDefinition) type).getTypes();
			Constraint constraint = constrainWith(dataType, "union");
			if (constraint != null) {
				for (TypeDefinition<?>t : types) {
					SimpleDataType unionType = translateSimpleDataType(name+"___"+t.getQName().getLocalName(), t);
					LiteralValueUse ut = tdlFactory.eINSTANCE.createLiteralValueUse();
//					ut.setValue(t.getQName().getLocalName());
					ut.setValue(unionType.getName());
					constraint.getQuantifier().add(ut);
				}
			}
			addMapping(type, dataType, sourceMappingTag, targetMappingTag);

			return dataType;
		} else {
			//TODO: add constraints?
			SimpleDataType dataType = getSimpleDataTypeFor(typeName);
			//TODO: what if extension is already present?
			if (baseType != null) {
				//add extension
				SimpleDataType superType = getSimpleDataTypeFor(baseType.getQName().getLocalName());
				Extension e = tdlFactory.eINSTANCE.createExtension();
				e.setExtending(superType);
				dataType.setExtension(e);
			}
			addMapping(type, dataType, sourceMappingTag, targetMappingTag);

			if (type instanceof StringTypeDefinition) {
				List<PatternConstraint> patternConstraints = ((StringTypeDefinition) type).getPatternConstraints();
				Constraint constraint = constrainWith(dataType, "pattern");
				if (constraint != null) {
					for (PatternConstraint c : patternConstraints) {
						LiteralValueUse ut = tdlFactory.eINSTANCE.createLiteralValueUse();
						//TODO: or getJavaPatternString?
						ut.setValue(c.getRegularExpressionString());
						constraint.getQuantifier().add(ut);
					}
				} else {
					//TODO: what if constraint is already there?
				}
			}
			
			return dataType;
		}
	}

	private void translateMember(String baseName, StructuredDataType dataType, DataSchemaNode child) {
		DataType memberType = translate(child, baseName);
		String propertyName = child.getQName().getLocalName();
		Member m = getContentWithName((String) propertyName, dataType, tdlPackage.Literals.MEMBER);
		m.setDataType(memberType);
		if (m.container() == null) {
			dataType.getMember().add(m);
		}
		
		if (child instanceof TypedDataSchemaNode) {
			TypeDefinition<?> type = ((TypedDataSchemaNode) child).getType();
			if (type instanceof LeafrefTypeDefinition) {
				String value = ((LeafrefTypeDefinition)type).getPathStatement().getOriginalString();
				annotateWith(m, LEAF_REF_PATH, value);
			}
		}
	}

	private StructuredDataType translateStructuredDataType(DataNodeContainer c, String baseName, String name) {
		StructuredDataType dataType = getStructuredDataTypeFor(name);
		addSuperType(dataType, this.messageBody);
		for (DataSchemaNode child : c.getChildNodes()) {
			translateMember(baseName, dataType, child);
		}
		addMapping((SchemaNode) c, dataType, sourceMappingTag, targetMappingTag);
		return dataType;
	}
	
	//----------------------------------------------------------
	//Mappings
	
	private void addMapping(SchemaNode c, DataType dataType, String sourceMappingTag, String targetMappingTag) {
//		String uri = c.toString().replaceAll(".+?argument=", "").replaceAll("}", "")
		
		QNameModule module = c.getQName().getModule();
		String resourceName = getCleanName(module.getNamespace().toString() + "_resource");
		DataResourceMapping resourceMapping = findElementOfType(resourceName, tdlPackage.Literals.DATA_RESOURCE_MAPPING);
		if (resourceMapping == null) {
			resourceMapping = tdlFactory.eINSTANCE.createDataResourceMapping();
			resourceMapping.setName(resourceName);
			String uri = module.getNamespace().toString();
			Optional<Revision> rev = module.getRevision();
			if (rev.isPresent())
				uri += "?" + rev.get().toString();
			resourceMapping.setResourceURI(uri);
			getGeneratedPackage().getPackagedElement().add(resourceMapping);

			if (this.mappingNameAnnotation != null) {
				Annotation annot = tdlFactory.eINSTANCE.createAnnotation();
				annot.setKey(this.mappingNameAnnotation);
				annot.setValue("Yang");
				resourceMapping.getAnnotation().add(annot);
			}
		}
		
		String uri = // c.toString().replaceAll(".+?argument=", "").replaceAll("}", "");
				c.getQName().getLocalName();
		
		//TODO: move mappings to a separate package
		
		// TODO: make configurable?
		boolean addSourceMapping = false;
		DataElementMapping sourceMapping = null;
		if (addSourceMapping)
			sourceMapping = addDataElementMapping(uri, dataType, sourceMappingTag, drm);

		DataElementMapping targetMapping = addDataElementMapping(uri, dataType, targetMappingTag, resourceMapping);

		if (dataType instanceof StructuredDataType) {
			//TODO: reflect source names before transformation
			for (Member m : ((StructuredDataType) dataType).getMember()) {
				String mUri = m.getName();
				if (c instanceof DataNodeContainer) {
					Collection<? extends DataSchemaNode> children = ((DataNodeContainer)c).getChildNodes();
					Optional<? extends DataSchemaNode> childNode = children.stream()
							.filter(cn -> getCleanName(cn.getQName().getLocalName()).equals(m.getName())).findAny();
					if (childNode.isPresent())
						mUri = childNode.get().getQName().getLocalName();
				}
				if (addSourceMapping)
					addParameterMapping(sourceMapping, m, mUri);
				addParameterMapping(targetMapping, m, mUri);
			}
		}
	}

	//----------------------------------------------------------
	//TODO: clean up below
	
	public static void main(String[] args) throws Exception {
        CrossSourceStatementReactor reactor = createReactor();

//        processStatic(reactor);

		BuildAction build = reactor.newBuild();
		Files.list(Path.of("samples", "ietf")).forEach(p -> 
			build.addSource(moduleFromPath(p.toString()))
		);
//		Files.list(Path.of("samples", "nfv")).forEach(p -> 
//			build.addSource(moduleFromPath(p.toString()))
//		);
		Files.list(Path.of("samples", "simple")).forEach(p -> 
			build.addSource(moduleFromPath(p.toString()))
		);
		
//		build.addSource(moduleFromPath("samples/nfv/etsi-nfv-descriptors.yang"));
		
		SchemaContext schemaContext = build.buildEffective();
//		
//		Set<Module> modules = schemaContext.getModules();
//		Set<DataSchemaNode> dataSchemaNodes = schemaContext.getDataDefinitions();
//		
//		dataSchemaNodes.forEach(System.out::println);
//		schemaContext.getChildNodes().forEach(System.out::println);
////		schemaContext.getTypeDefinitions().forEach(System.out::println);
		expand("  ", schemaContext);
	}

	private static CrossSourceStatementReactor createReactor() {
		CrossSourceStatementReactor reactor = RFC7950Reactors.vanillaReactorBuilder()
        .addStatementSupport(ModelProcessingPhase.FULL_DECLARATION,
            new MountStatementSupport(YangParserConfiguration.DEFAULT))
        .build();
		return reactor;
	}

	private static void processStatic(CrossSourceStatementReactor reactor) throws ReactorException {
		reactor.newBuild().addSources(
//                moduleFromResources("/ietf/ietf-yang-types@2013-07-15.yang"),
//                moduleFromResources("/ietf/ietf-restconf@2017-01-26.yang"),
//                moduleFromResources("/ietf/ietf-inet-types@2013-07-15.yang"),
//                moduleFromResources("/nfv/etsi-nfv-common.yang"),
//                moduleFromResources("/nfv/etsi-nfv-nsd.yang"),
//                moduleFromResources("/nfv/etsi-nfv-pnfd.yang"),
//                moduleFromResources("/nfv/etsi-nfv-vnfd.yang"),
//                moduleFromResources("/nfv/etsi-nfv-descriptors.yang"),
//                moduleFromResources("/nfv/etsi-nfv-ns.yang"),
//                moduleFromResources("/nfv/etsi-nfv-pnf.yang"), 
//                moduleFromResources("/nfv/etsi-nfv-vnf.yang")
                moduleFromPath("samples/ietf/ietf-yang-types@2013-07-15.yang"),
                moduleFromPath("samples/ietf/ietf-restconf@2017-01-26.yang"),
                moduleFromPath("samples/ietf/ietf-inet-types@2013-07-15.yang"),
                moduleFromPath("samples/nfv/etsi-nfv-common.yang"),
                moduleFromPath("samples/nfv/etsi-nfv-nsd.yang"),
                moduleFromPath("samples/nfv/etsi-nfv-pnfd.yang"),
                moduleFromPath("samples/nfv/etsi-nfv-vnfd.yang"),
                moduleFromPath("samples/nfv/etsi-nfv-descriptors.yang"),
                moduleFromPath("samples/nfv/etsi-nfv-ns.yang"),
                moduleFromPath("samples/nfv/etsi-nfv-pnf.yang"), 
                moduleFromPath("samples/nfv/etsi-nfv-vnf.yang")
            )
                // moduleFromResources("/simple/sports.yang"))
            .buildEffective()
            .getChildNodes().forEach(e->System.out.println(e.getQName()));
            ;
	}
	
    private static YangStatementStreamSource moduleFromPath(String path) {
    	return moduleFromPath(Path.of(path));
    }

    private static YangStatementStreamSource moduleFromPath(Path path) {
        try {
            // return new YangStatementSourceImpl("/example.yang", false);
            return YangStatementStreamSource.create(new FileYangTextSource(path));
            // return YangStatementStreamSource.create(YangTextSource.forResource(path));
        } catch (final YangSyntaxErrorException | IOException e) {
            throw new IllegalStateException("Failed to find resource " + path.toString(), e);
        }
    }
	
	public static void expand(String prefix, ContainerLike node) {
		for (DataSchemaNode c : node.getChildNodes()) {
			expand(prefix, c);
		}
	}

	public static void expand(String prefix, ListSchemaNode node) {
		for (DataSchemaNode c : node.getChildNodes()) {
			expand(prefix, c);
		}
	}

	private static void expand(String prefix, DataSchemaNode c) {
		//TODO: this is not accurately reflecting the structures..
		if (c instanceof LeafListSchemaNode) {
			System.out.println(prefix+c.getQName().getLocalName() + " : " + ((LeafListSchemaNode) c).getType().getQName());
		} else if (c instanceof ContainerSchemaNode) {
			System.out.println("!c"+prefix+c.getQName().getLocalName() + " : " );
			//create structured data type
			expand(prefix+"  ", (ContainerSchemaNode) c);
		} else if (c instanceof ListSchemaNode) {
			System.out.println("!l"+prefix+c.getQName().getLocalName() + " : " );
			//create collection of structured data type
			expand(prefix+"  ", (ListSchemaNode) c);
		} else if (c instanceof RegularLeafEffectiveStatement) {
			System.out.println("!r"+prefix+c.getQName().getLocalName() + " : " + ((RegularLeafEffectiveStatement) c).getDeclared().getType().argument());
			//create member of reference data type?
		} else if (c instanceof EmptyLeafEffectiveStatement) {
			System.out.println("!e"+prefix+c.getQName().getLocalName() + " : " + ((EmptyLeafEffectiveStatement) c).getType().getQName());
			//create member of data type?
		} else if (c instanceof LeafrefTypeDefinition) {
			//not working?
			System.out.println(((LeafrefTypeDefinition) c).getPathStatement().getOriginalString());
		} else if (c instanceof LeafSchemaNode) {
			TypeDefinition<? extends TypeDefinition<?>> type = ((LeafSchemaNode)c).getType();
			String t = type.getQName().getLocalName();
			if (!type.getQName().getLocalName().equals("string")) {
				t = type.getQName().toString();
				System.out.println("!x"+((Default<QName,LeafStatement>) c).getDeclared().getType());
			} 
			System.out.println("!"+prefix+c.getQName().getLocalName() + " : " + t);
		} else {
			System.out.println(prefix+c.getQName().getLocalName() + " : " + c.getClass().getSimpleName());
		}
	}

}
