/*
 * Copyright 2016 Richard Cartwright
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * $Log: XSDGenerator.java,v $
 * Revision 1.1  2011/07/27 17:07:21  vizigoth
 * Additions and alterations coincidence with editing the Reg-XML document part 1.
 *
 */

package tv.amwa.maj.io.xml;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.XMLConstants;

import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Element;

import tv.amwa.maj.constant.CommonConstants;
import tv.amwa.maj.industry.MediaEngine;
import tv.amwa.maj.industry.Warehouse;
import tv.amwa.maj.meta.ClassDefinition;
import tv.amwa.maj.meta.MetaDefinition;
import tv.amwa.maj.meta.PropertyDefinition;
import tv.amwa.maj.meta.TypeDefinition;
import tv.amwa.maj.meta.TypeDefinitionCharacter;
import tv.amwa.maj.meta.TypeDefinitionEnumeration;
import tv.amwa.maj.meta.TypeDefinitionExtendibleEnumeration;
import tv.amwa.maj.meta.TypeDefinitionFixedArray;
import tv.amwa.maj.meta.TypeDefinitionIndirect;
import tv.amwa.maj.meta.TypeDefinitionInteger;
import tv.amwa.maj.meta.TypeDefinitionOpaque;
import tv.amwa.maj.meta.TypeDefinitionRecord;
import tv.amwa.maj.meta.TypeDefinitionRename;
import tv.amwa.maj.meta.TypeDefinitionSet;
import tv.amwa.maj.meta.TypeDefinitionStream;
import tv.amwa.maj.meta.TypeDefinitionString;
import tv.amwa.maj.meta.TypeDefinitionStrongObjectReference;
import tv.amwa.maj.meta.TypeDefinitionVariableArray;
import tv.amwa.maj.meta.TypeDefinitionWeakObjectReference;

// TODO comments
// TODO tests

/**
 * <p>Generate an XML schema for validating a Reg-XML document. The schema may be generated
 * just for the baseline model or for extension data models.</p>
 * 
 *
 * 
 * @see MetaDictionaryGenerator
 */
public class XSDGenerator 
	implements CommonConstants {

	public final static String XSD_NAMESPACE = "http://www.w3.org/2001/XMLSchema";
	public final static String XSD_PREFIX = "xs";
	public final static String XLINK_NAMESPACE = "http://www.w3.org/1999/xlink";
	public final static String XLINK_PREFIX = "xlink";
	
	public final static DocumentFragment generateBaslineXSD() {
		
		MediaEngine.initializeAAF();
		
		Collection<ClassDefinition> baselineClasses = MetaDictionaryGenerator.makeBaselineClasses();
		
		return generateXSD(baselineClasses, AAF_XML_NAMESPACE, AAF_XML_PREFIX, AAFElement.AAF_TAG, "Preface");
	}
	
	public final static String generateBaselineXSDAsAString() {
		
		return XMLBuilder.transformNodeToString(generateBaslineXSD());
	}
	
	public final static DocumentFragment generateXSD(
			Collection<ClassDefinition> classes,
			String targetNamespace,
			String targetPrefix,
			String rootElement,
			String rootClass) 
		throws NullPointerException {
		
		if (classes == null)
			throw new NullPointerException("Cannot create a Reg-XML XSD for a null set of class definitions.");
		
		if (targetNamespace == null)
			throw new NullPointerException("Cannot create a Reg-XML XSD for a null target namespace.");
		
		DocumentFragment xsdFragment = XMLBuilder.createDocumentFragment();
		xsdFragment.appendChild(xsdFragment.getOwnerDocument().createComment(
				"Schema generated by the Media Authoring with Java API (MAJ API)"));
		
		Element schemaElement = XMLBuilder.createChild(xsdFragment, XSD_NAMESPACE, XSD_PREFIX, "schema");
		XMLBuilder.setAttribute(schemaElement, "", "", "targetNamespace", targetNamespace);
		XMLBuilder.setAttribute(schemaElement, "", "", "elementFormDefault", "qualified");
		XMLBuilder.setAttribute(schemaElement, "", "", "attributeFormDefault", "unqualified");
		schemaElement.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:" + XLINK_PREFIX, XLINK_NAMESPACE);
		schemaElement.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:" + targetPrefix, targetNamespace);		
		schemaElement.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:reg", targetNamespace);		
		
		Element includeElement = XMLBuilder.createChild(schemaElement, XSD_NAMESPACE, XSD_PREFIX, "include");
		XMLBuilder.setAttribute(includeElement, "", "", "schemaLocation", "metadict.xsd");

		Element importElement = XMLBuilder.createChild(schemaElement, XSD_NAMESPACE, XSD_PREFIX, "import");
		XMLBuilder.setAttribute(importElement, "", "", "namespace", XLINK_NAMESPACE);
		XMLBuilder.setAttribute(importElement, "", "", "schemaLocation", "xlink.xsd");
		
		makeDataTypes(schemaElement, targetNamespace, targetPrefix);
		makeGlobalAttributes(schemaElement, targetNamespace, targetPrefix);
		makeRootElement(schemaElement, targetNamespace, targetPrefix, rootElement, rootClass);
		makeRecordTypes(schemaElement, targetNamespace, targetPrefix);
		
		List<ClassDefinition> classList = new ArrayList<ClassDefinition>(classes.size() * 2);
		List<PropertyDefinition> propertyList = new ArrayList<PropertyDefinition>(classes.size() * 20);
		
		List<TypeDefinitionInteger> integerList = new ArrayList<TypeDefinitionInteger>();
		List<TypeDefinitionEnumeration> enumerationList = new ArrayList<TypeDefinitionEnumeration>();
		List<TypeDefinitionCharacter> characterList = new ArrayList<TypeDefinitionCharacter>();
		List<TypeDefinitionString> stringList = new ArrayList<TypeDefinitionString>();
		List<TypeDefinitionExtendibleEnumeration> extEnumList = new ArrayList<TypeDefinitionExtendibleEnumeration>();
		List<TypeDefinitionIndirect> indirectList = new ArrayList<TypeDefinitionIndirect>();
		List<TypeDefinitionOpaque> opaqueList = new ArrayList<TypeDefinitionOpaque>();
		List<TypeDefinitionStream> streamList = new ArrayList<TypeDefinitionStream>();
		List<TypeDefinitionWeakObjectReference> weakRefList = new ArrayList<TypeDefinitionWeakObjectReference>();
		List<TypeDefinitionStrongObjectReference> strongRefList = new ArrayList<TypeDefinitionStrongObjectReference>();
		List<TypeDefinitionRecord> recordList = new ArrayList<TypeDefinitionRecord>();
		List<TypeDefinitionFixedArray> fixedArrayList = new ArrayList<TypeDefinitionFixedArray>();
		List<TypeDefinitionSet> setList = new ArrayList<TypeDefinitionSet>();
		List<TypeDefinitionVariableArray> variableArrayList = new ArrayList<TypeDefinitionVariableArray>();
		List<TypeDefinitionRename> renameList = new ArrayList<TypeDefinitionRename>();
		
		Set<MetaDefinition> baseline = new HashSet<MetaDefinition>();
		// TODO defending against bad behavior of extension properties in core classes - could do better?
		boolean aafMode = false;
		if ((rootElement != null) && (rootElement.equals(AAFElement.AAF_TAG))) aafMode = true;
		
		for ( ClassDefinition classItem : classes ) 
			MetaDictionaryGenerator.addPropertiesAndTypes(classItem, baseline, classList, propertyList, aafMode);
		
		boolean allTypesChecked = false;
		while (allTypesChecked == false) {
	
			Set<MetaDefinition> newForBaseline = new HashSet<MetaDefinition>();
			allTypesChecked = true;
			
			for ( MetaDefinition metaDefinition : baseline ) {
	
				if (!(metaDefinition instanceof TypeDefinition)) continue;
				TypeDefinition typeDefinition = (TypeDefinition) metaDefinition;
	
				switch (typeDefinition.getTypeCategory()) {
	
				case Int:
					if (!integerList.contains(typeDefinition))
						integerList.add((TypeDefinitionInteger) typeDefinition);
					break;
				case Enum:
					if (!enumerationList.contains(typeDefinition))
						enumerationList.add((TypeDefinitionEnumeration) typeDefinition);
					
					TypeDefinitionEnumeration enumerationType = (TypeDefinitionEnumeration) typeDefinition;
					if (!baseline.contains(enumerationType.getElementType())) {
						newForBaseline.add(enumerationType.getElementType());
						allTypesChecked = false;
					}
					break;
				case Character:
					if (!characterList.contains(typeDefinition))
						characterList.add((TypeDefinitionCharacter) typeDefinition);
					break;
				case String:
					if (!stringList.contains(typeDefinition))
						stringList.add((TypeDefinitionString) typeDefinition);
					
					TypeDefinitionString stringType = (TypeDefinitionString) typeDefinition;
					if (!baseline.contains(stringType.getElementType())) {
						newForBaseline.add(stringType.getElementType());
						allTypesChecked = false;
					}
					break;
				case ExtEnum:
					if (!extEnumList.contains(typeDefinition))
						extEnumList.add((TypeDefinitionExtendibleEnumeration) typeDefinition);
					break;
				case Indirect:
					if (!indirectList.contains(typeDefinition))
						indirectList.add((TypeDefinitionIndirect) typeDefinition);
					break;
				case Opaque:
					if (!opaqueList.contains(typeDefinition))
						opaqueList.add((TypeDefinitionOpaque) typeDefinition);
					break;
				case Stream:
					if (!streamList.contains(typeDefinition))
						streamList.add((TypeDefinitionStream) typeDefinition);
					break;
				case Record:
					if (!recordList.contains(typeDefinition))
						recordList.add((TypeDefinitionRecord) typeDefinition);
					
					TypeDefinitionRecord recordType = (TypeDefinitionRecord) typeDefinition;
					for ( int u = 0 ; u < recordType.getCount() ; u++ )
						if (!baseline.contains(recordType.getMemberType(u))) {
							newForBaseline.add(recordType.getMemberType(u));
							allTypesChecked = false;
						}
					break;
				case WeakObjRef:
					if (!weakRefList.contains(typeDefinition))
						weakRefList.add((TypeDefinitionWeakObjectReference) typeDefinition);
					
					TypeDefinitionWeakObjectReference weakRefType = (TypeDefinitionWeakObjectReference) typeDefinition;
					if (!classList.contains(weakRefType.getObjectType())) {
						MetaDictionaryGenerator.addPropertiesAndTypes(weakRefType.getObjectType(), newForBaseline, classList, propertyList, aafMode);
						allTypesChecked = false;
					}
					break;
				case StrongObjRef:
					if (!strongRefList.contains(typeDefinition))
						strongRefList.add((TypeDefinitionStrongObjectReference) typeDefinition);
					
					TypeDefinitionStrongObjectReference strongRefType = (TypeDefinitionStrongObjectReference) typeDefinition;
					if (!classList.contains(strongRefType.getObjectType())) {
						MetaDictionaryGenerator.addPropertiesAndTypes(strongRefType.getObjectType(), newForBaseline, classList, propertyList, aafMode);
						allTypesChecked = false;
					}
					break;
				case FixedArray:
					if (!fixedArrayList.contains(typeDefinition))
						fixedArrayList.add((TypeDefinitionFixedArray) typeDefinition);
					
					TypeDefinitionFixedArray fixedArrayType = (TypeDefinitionFixedArray) typeDefinition;
					if (!baseline.contains(fixedArrayType.getType())) {
						newForBaseline.add(fixedArrayType.getType());
						allTypesChecked = false;
					}
					break;
				case Set:
					if (!setList.contains(typeDefinition))
						setList.add((TypeDefinitionSet) typeDefinition);
					
					TypeDefinitionSet setType = (TypeDefinitionSet) typeDefinition;
					if (!baseline.contains(setType.getElementType())) {
						newForBaseline.add(setType.getElementType());
						allTypesChecked = false;
					}
					break;
				case VariableArray:
					if (!variableArrayList.contains(typeDefinition))
						variableArrayList.add((TypeDefinitionVariableArray) typeDefinition);
					
					TypeDefinitionVariableArray variableArrayType = (TypeDefinitionVariableArray) typeDefinition;
					if (!baseline.contains(variableArrayType.getType())) {
						newForBaseline.add(variableArrayType.getType());
						allTypesChecked = false;
					}
					break;
				case Rename:
					if (!renameList.contains(typeDefinition))
						renameList.add((TypeDefinitionRename) typeDefinition);
					
					TypeDefinitionRename renameType = (TypeDefinitionRename) typeDefinition;
					if (!baseline.contains(renameType.getBaseType())) {
						newForBaseline.add(renameType.getBaseType());
						allTypesChecked = false;
					}
					break;
				default:
					break;
				}
			} // For all meta definitions in the baseline.
			
			baseline.addAll(newForBaseline);
		} // While allTypesChecked == false
		 		
		Map<ClassDefinition, Set<ClassDefinition>> typeHierarchy =
			buildTypeHierarchy(classList);
		
		for ( ClassDefinition classItem : classList )
			makeClassElement(schemaElement, classItem, targetNamespace, targetPrefix, aafMode);
		
		for ( PropertyDefinition propertyItem : propertyList )
				makePropertyElement(schemaElement, propertyItem, targetNamespace, targetPrefix);
		
		for ( TypeDefinitionInteger integerType : integerList )
			makeIntegerTypeElement(schemaElement, integerType, targetPrefix);
		for ( TypeDefinitionEnumeration enumerationType : enumerationList )
			makeEnumerationTypeElement(schemaElement, enumerationType, targetPrefix);
		for ( TypeDefinitionCharacter characterType : characterList )
			makeCharacterTypeElement(schemaElement, characterType, targetPrefix);
		for ( TypeDefinitionString stringType : stringList )
			makeCharacterTypeElement(schemaElement, stringType, targetPrefix);
		for ( TypeDefinitionExtendibleEnumeration extEnumType : extEnumList ) 
			makeExtendibleEnumerationTypeElement(schemaElement, extEnumType, targetPrefix);
		for ( TypeDefinitionIndirect indirectType : indirectList )
			makeIndirectTypeElement(schemaElement, indirectType, targetPrefix);
		for ( TypeDefinitionOpaque opaqueType : opaqueList )
			makeOpaqueTypeElement(schemaElement, opaqueType, targetPrefix);
		for ( TypeDefinitionStream streamType : streamList )
			makeStreamTypeElement(schemaElement, streamType, targetPrefix);
		for ( TypeDefinitionFixedArray fixedArrayType : fixedArrayList )
			makeFixedArrayTypeElement(schemaElement, fixedArrayType, targetPrefix, typeHierarchy);
		for ( TypeDefinitionRecord recordType : recordList )
			makeRecordTypeElement(schemaElement, recordType, targetPrefix);
		for ( TypeDefinitionWeakObjectReference weakRefType : weakRefList )
			makeWeakReferenceTypeElement(schemaElement, weakRefType, targetPrefix);
		for ( TypeDefinitionStrongObjectReference strongRefType : strongRefList)
			makeStrongReferenceTypeElement(schemaElement, strongRefType, targetPrefix, typeHierarchy);
		for ( TypeDefinitionSet setType : setList )
			makeSetTypeElement(schemaElement, setType, targetPrefix, typeHierarchy);
		for ( TypeDefinitionVariableArray variableArrayType : variableArrayList )
			makeVariableArrayTypeElement(schemaElement, variableArrayType, targetPrefix, typeHierarchy, stringList);
		for ( TypeDefinitionRename renameType : renameList )
			makeRenameTypeElement(schemaElement, renameType, targetPrefix);
		
		makeReferenceElements(schemaElement, variableArrayList, fixedArrayList, setList, stringList, targetPrefix);
		
		return xsdFragment;
	}
	
	public final static String generateXSDAsAString(
			Collection<ClassDefinition> classes,
			String targetNamespace,
			String targetPrefix,
			String rootElement,
			String rootClass)
		throws NullPointerException {
		
		return XMLBuilder.transformNodeToString(generateXSD(classes, targetNamespace, targetPrefix, rootElement, rootClass));
	}
	
	static void makeRootElement(
			Element schemaElement,
			String targetNamespace, 
			String targetPrefix,
			String rootElementName,
			String rootClassName) {
		
		Element rootElement = element(rootElementName,
				complexType(
						sequence(
								optionalElement("Extensions",
										complexType(
												sequence(
														listRefElement(targetPrefix, "Extension", end(schemaElement))))),
								elementRef(targetPrefix, rootClassName, end(schemaElement))),
						requiredAttribute("version", targetPrefix, "VersionType", end(schemaElement))));
		
		Element uniqueID = XMLBuilder.createChild(rootElement, XSD_NAMESPACE, XSD_PREFIX, "unique");
		XMLBuilder.setAttribute(uniqueID, "", "", "name", "_U_MDSchemeID");
		Element selector = XMLBuilder.createChild(uniqueID, XSD_NAMESPACE, XSD_PREFIX, "selector");
		XMLBuilder.setAttribute(selector, "", "", "xpath", targetPrefix + ":Extensions/" + targetPrefix + ":Extension");	
		Element field = XMLBuilder.createChild(uniqueID, XSD_NAMESPACE, XSD_PREFIX, "field");
		XMLBuilder.setAttribute(field, "", "", "xpath", targetPrefix + ":SchemeID");
		
		Element uniqueURI = XMLBuilder.createChild(rootElement, XSD_NAMESPACE, XSD_PREFIX, "unique");
		XMLBuilder.setAttribute(uniqueURI, "", "", "name", "_U_MDSchemeURI");
		selector = XMLBuilder.createChild(uniqueURI, XSD_NAMESPACE, XSD_PREFIX, "selector");
		XMLBuilder.setAttribute(selector, "", "", "xpath", targetPrefix + ":Extensions/" + targetPrefix + ":Extension");	
		field = XMLBuilder.createChild(uniqueURI, XSD_NAMESPACE, XSD_PREFIX, "field");
		XMLBuilder.setAttribute(field, "", "", "xpath", targetPrefix + ":SchemeURI");
		
		schemaElement.appendChild(rootElement);
	}
	
	final static void makeRecordTypes(
			Element schemaElement,
			String targetNamespace, 
			String targetPrefix) {

		
	}

	final static void makeDataTypes(
			Element schemaElement,
			String targetNamespace,
			String targetPrefix) {
		
		schemaElement.appendChild(
				simpleType("TargetType", 
						union("xs:token", 
								simpleType(
										restriction(targetPrefix, "AUID", end(schemaElement))),
								simpleType(
										restriction(targetPrefix, "PackageIDType", end(schemaElement))),
								simpleType(
										restriction(XSD_PREFIX, "string", 
												pattern("([^\\s]+\\s)?[^\\s]+", end(schemaElement)))))));
		schemaElement.appendChild(
				simpleType("ByteOrderType",
						restriction(XSD_PREFIX, "string", 
								enumeration("BigEndian", end(schemaElement)),
								enumeration("LittleEndian", end(schemaElement)))));
		
		schemaElement.appendChild(
				simpleType("HexByteArrayType",
						restriction(XSD_PREFIX, "string", 
								pattern("(\\s*[0-9a-fA-F][0-9a-fA-F])*\\s*", end(schemaElement)))));
	}
	
	final static void makeGlobalAttributes(
			Element schemaElement,
			String targetNamespace,
			String targetPrefix) {
		
		schemaElement.appendChild(attribute("uid", targetPrefix, "TargetType", end(schemaElement)));
		schemaElement.appendChild(attribute("byteOrder", targetPrefix, "ByteOrderType", end(schemaElement)));
		schemaElement.appendChild(attribute("stream", XSD_PREFIX, "ENTITY", end(schemaElement)));
		schemaElement.appendChild(attribute("actualType", targetPrefix, "TargetType", end(schemaElement)));
		schemaElement.appendChild(attribute("escaped", XSD_PREFIX, "boolean", end(schemaElement)));
		schemaElement.appendChild(attribute("path", XSD_PREFIX, "string", end(schemaElement)));
	}
	
	final static void makeClassElement(
			Element schemaElement,
			ClassDefinition classItem,
			String targetNamespace,
			String targetPrefix, 
			boolean aafMode) {
		
		Element uid = attributeRefRequired(targetPrefix, "uid", end(schemaElement));
		Element path = attributeRefOptional(targetPrefix, "path", end(schemaElement));
		
		Document document = schemaElement.getOwnerDocument();
		Element all = document.createElementNS(XSD_NAMESPACE, XSD_PREFIX + ":all");
		
		for ( PropertyDefinition propertyItem : classItem.getAllPropertyDefinitions() ) {
			if ((aafMode) && (!MediaEngine.isBaseline(propertyItem))) continue;
			if ((propertyItem.getIsOptional()) || 
					((aafMode) && (propertyItem.getSymbol().equals("ObjectClass"))))
				all.appendChild(optionalElementRef(targetPrefix, propertyItem.getSymbol(), end(schemaElement)));
			else
				all.appendChild(elementRef(targetPrefix, propertyItem.getSymbol(), end(schemaElement)));
		}
		
		Element complexType = (classItem.isUniquelyIdentified()) ?
				complexType(all, uid, path) : complexType(all, path);

		if (classItem.isConcrete())
			schemaElement.appendChild(
					element(classItem.getSymbol(), complexType));
		else
			schemaElement.appendChild(
					abstractElement(classItem.getSymbol(), complexType));
								
	}
	
	final static void makePropertyElement(
			Element schemaElement,
			PropertyDefinition propertyItem,
			String targetNamespace,
			String targetPrefix) {
		
		if ((propertyItem.getMemberOf().getSymbol().equals("Preface")) &&
				(propertyItem.getSymbol().equals("ByteOrder"))) {
			schemaElement.appendChild(
					element("ByteOrder", targetPrefix, "ByteOrderType", end(schemaElement)));
			return;
		}
		
		// TODO deal with property wrapper definitions here
		
		schemaElement.appendChild(
				element(propertyItem.getSymbol(), targetPrefix, propertyItem.getTypeDefinition().getSymbol(),
						end(schemaElement)));
	}
	
	final static void makeIntegerTypeElement(
			Element schemaElement,
			TypeDefinitionInteger integerType,
			String targetPrefix) {
		
		String xsdlIntegerTypeName = null;
		switch (integerType.getSize()) {
		
		case 1:
			xsdlIntegerTypeName = (integerType.isSigned()) ? "byte" : "unsignedByte";
			break;
		case 2:
			xsdlIntegerTypeName = (integerType.isSigned()) ? "short" : "unsignedShort";
			break;
		case 4:
			xsdlIntegerTypeName = (integerType.isSigned()) ? "integer" : "unsignedInt";
			break;
		case 8:
		default:
			xsdlIntegerTypeName = (integerType.isSigned()) ? "long" : "unsignedLong";
			break;
		}
		
		schemaElement.appendChild(
				simpleType(integerType.getSymbol(),
						union(XSD_PREFIX + ":" + xsdlIntegerTypeName, 
								simpleType(
										restriction(XSD_PREFIX, "string",
												pattern("0x[0-9a-fA-F]{1," + (integerType.getSize() * 2) + "}",
														end(schemaElement)))))));
	}
			
	final static void makeEnumerationTypeElement(
			Element schemaElement,
			TypeDefinitionEnumeration enumerationType,
			String targetPrefix) {
		
		Element restriction = restriction(XSD_PREFIX, "token", end(schemaElement));
		
		for ( int x = 0 ; x < enumerationType.countElements() ; x++ ) {
			
			String elementName = enumerationType.getElementName(x);
			if (elementName.startsWith("_")) elementName = elementName.substring(1);
			restriction.appendChild(
					enumeration(elementName, end(schemaElement)));
		}
		
		schemaElement.appendChild(
				simpleType(enumerationType.getSymbol(), restriction));
	}
	
	final static void makeCharacterTypeElement(
			Element schemaElement,
			TypeDefinition characterType,
			String targetPrefix) {
		
		schemaElement.appendChild(
				complexType(characterType.getSymbol(),
						simpleContent(
								extension(XSD_PREFIX, "string", 
										attributeRefOptional(targetPrefix, "escaped", end(schemaElement))))));
	}
	
	// TODO does not support processing of extensions
	
	final static void makeExtendibleEnumerationTypeElement(
			Element schemaElement,
			TypeDefinitionExtendibleEnumeration extEnumType,
			String targetPrefix) {
		
		Element restriction = restriction(XSD_PREFIX, "string", end(schemaElement));
		for ( int x = 0 ; x < extEnumType.countElements() ; x++ )
			restriction.appendChild(
					enumeration(
							Warehouse.extendibleEnumerationFullName(extEnumType.getElementValue(x)), 
							end(schemaElement)));
		
		schemaElement.appendChild(
				simpleType(extEnumType.getSymbol(),
						union(
								simpleType(restriction))));
								
	}
		
	final static void makeIndirectTypeElement(
			Element schemaElement,
			TypeDefinitionIndirect indirectType,
			String targetPrefix) {
		
		Document document = schemaElement.getOwnerDocument();
		Element any = document.createElementNS(XSD_NAMESPACE, XSD_PREFIX + ":any");
		XMLBuilder.setAttribute(any, "", "", "minOccurs", "0");
		XMLBuilder.setAttribute(any, "", "", "maxOccurs", "unbounded");
		XMLBuilder.setAttribute(any, "", "", "processContents", "skip");
		
		schemaElement.appendChild(
				complexType(indirectType.getSymbol(), 
						mixedComplexContent(
								restriction(XSD_PREFIX, "anyType", 
										sequence(any),
										attributeRefRequired(targetPrefix, "actualType", end(schemaElement)),
										attributeRefOptional(targetPrefix, "escaped", end(schemaElement))))));
	}

	final static void makeOpaqueTypeElement(
			Element schemaElement,
			TypeDefinitionOpaque opaqueType,
			String targetPrefix) {

		schemaElement.appendChild(
				complexType(opaqueType.getSymbol(), 
						simpleContent(
								extension(targetPrefix, "HexByteArrayType", 
										attributeRefRequired(targetPrefix, "actualType", end(schemaElement)),
										attributeRefRequired(targetPrefix, "byteOrder", end(schemaElement))))));
	}
	
	final static void makeStreamTypeElement(
			Element schemaElement,
			TypeDefinitionStream streamType,
			String targetPrefix) {
		
		schemaElement.appendChild(
				complexType(streamType.getSymbol(),
						attributeRefOptional(targetPrefix, "stream", end(schemaElement)),
						attributeRefOptional("xlink", "href", end(schemaElement)),
						attributeRefOptional(targetPrefix, "byteOrder", end(schemaElement))));
	}
	
	final static void makeFixedArrayTypeElement(
			Element schemaElement,
			TypeDefinitionFixedArray fixedArrayType,
			String targetPrefix, 
			Map<ClassDefinition, Set<ClassDefinition>> typeHierarchy) {
	
		if (!(fixedArrayType.getType() instanceof TypeDefinitionStrongObjectReference)) {
			
			Element element = elementRef(targetPrefix, fixedArrayType.getType().getSymbol(), end(schemaElement));
			XMLBuilder.setAttribute(element, "", "", "minOccurs", Integer.toString(fixedArrayType.getCount()));
			XMLBuilder.setAttribute(element, "", "", "maxOccurs", Integer.toString(fixedArrayType.getCount()));
			
			schemaElement.appendChild(
					complexType(fixedArrayType.getSymbol(), 
							sequence(element)));
			return;
		}
		
		Element choice = choice(end(schemaElement));
		XMLBuilder.setAttribute(choice, "", "", "minOccurs", Integer.toString(fixedArrayType.getCount()));
		XMLBuilder.setAttribute(choice, "", "", "maxOccurs", Integer.toString(fixedArrayType.getCount()));

		Set<ClassDefinition> descendents = getAllDescendents(
				((TypeDefinitionStrongObjectReference) fixedArrayType.getType()).getObjectType(), typeHierarchy);
		descendents.add(((TypeDefinitionStrongObjectReference) fixedArrayType.getType()).getObjectType());
		
		for ( ClassDefinition child : descendents ) {
			if (child.isConcrete())
				choice.appendChild(elementRef(targetPrefix, child.getSymbol(), end(schemaElement)));
		}
		
		schemaElement.appendChild(
				complexType(fixedArrayType.getSymbol(), choice));
	}
	
	final static void makeRecordTypeElement(
			Element schemaElement,
			TypeDefinitionRecord recordType,
			String targetPrefix) {
		
		if (recordType.getSymbol().equals("AUID")) {
			makeAUIDType(schemaElement, recordType, targetPrefix);
			return;
		}
		
		if (recordType.getSymbol().equals("DateStruct")) {
			makeDateStructType(schemaElement, recordType, targetPrefix);
			return;
		}

		if (recordType.getSymbol().equals("PackageIDType")) {
			makePackageIDType(schemaElement, recordType, targetPrefix);
			return;
		}
		
		if (recordType.getSymbol().equals("Rational")) {
			makeRationalType(schemaElement, recordType, targetPrefix);
			return;
		}
		
		if (recordType.getSymbol().equals("TimeStruct")) {
			makeTimeStructType(schemaElement, recordType, targetPrefix);
			return;
		}
		
		if (recordType.getSymbol().equals("TimeStamp")) {
			makeTimeStampType(schemaElement, recordType, targetPrefix);
			return;
		}
		
		if (recordType.getSymbol().equals("VersionType")) {
			makeVersionType(schemaElement, recordType, targetPrefix);
			return;
		}
	
		Element sequence = sequence(end(schemaElement));
		for ( int x = 0 ; x < recordType.getCount() ; x++ ) {
			sequence.appendChild(
					element(upperFirstLetter(recordType.getMemberName(x)), 
							targetPrefix, recordType.getMemberType(x).getSymbol(), end(schemaElement)));
		}
		
		schemaElement.appendChild(
				complexType(recordType.getSymbol(), sequence));
	}
	
	final static void makeAUIDType(
			Element schemaElement,
			TypeDefinitionRecord recordType,
			String targetPrefix) {
		
		schemaElement.appendChild(
				simpleType("AUID", 
						restriction(XSD_PREFIX, "anyURI", 
								pattern("urn:smpte:ul:([0-9a-fA-F]{8}\\.){3}[0-9a-fA-F]{8}", end(schemaElement)),
								pattern("urn:uuid:[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}", end(schemaElement)))));
	}
	
	final static void makeDateStructType(
			Element schemaElement,
			TypeDefinitionRecord recordType,
			String targetPrefix) {
	
		schemaElement.appendChild(
				simpleType("DateStruct",
						union(
								simpleType(
										restriction(XSD_PREFIX, "date", 
												pattern(".+(((\\+|\\-)\\d\\d:\\d\\d)|Z)", end(schemaElement)))),
								simpleType(
										restriction(XSD_PREFIX, "string", 
												pattern("0000-00-00Z", end(schemaElement)))))));
	}
	
	final static void makePackageIDType(
			Element schemaElement,
			TypeDefinitionRecord recordType,
			String targetPrefix) {
		
		schemaElement.appendChild(
				simpleType("PackageIDType",
						restriction(XSD_PREFIX, "string",
								pattern("urn:smpte:umid:([0-9a-fA-F]{8}\\.){7}[0-9a-fA-F]{8}", end(schemaElement)))));
	}
	
	final static void makeRationalType(
			Element schemaElement,
			TypeDefinitionRecord recordType,
			String targetPrefix) {
	
		schemaElement.appendChild(
				simpleType("Rational",
						restriction(XSD_PREFIX, "string", 
								pattern("\\-?\\d{1,10}(/\\-?\\d{1,10})?", end(schemaElement)))));
	}
	
	final static void makeTimeStructType(
			Element schemaElement,
			TypeDefinitionRecord recordType,
			String targetPrefix) {
		
		schemaElement.appendChild(
				simpleType("TimeStruct",
						union(
								simpleType(
										restriction(XSD_PREFIX, "time", 
												pattern(".+(((\\+|\\-)\\d\\d:\\d\\d)|Z)", end(schemaElement)))),
								simpleType(
										restriction(XSD_PREFIX, "string", 
												pattern("00:00:00Z", end(schemaElement)))))));

	}
	
	final static void makeTimeStampType(
			Element schemaElement,
			TypeDefinitionRecord recordType,
			String targetPrefix) {
		
		schemaElement.appendChild(
				simpleType("TimeStamp",
						union(
								simpleType(
										restriction(XSD_PREFIX, "dateTime", 
												pattern(".+(((\\+|\\-)\\d\\d:\\d\\d)|Z)", end(schemaElement)))),
								simpleType(
										restriction(XSD_PREFIX, "string", 
												pattern("0000-00-00T00:00:00Z", end(schemaElement)))))));
	}
	
	final static void makeVersionType(
			Element schemaElement,
			TypeDefinitionRecord recordType,
			String targetPrefix) {
		
		schemaElement.appendChild(
				simpleType("VersionType",
						restriction(XSD_PREFIX, "string", 
								pattern("\\-?\\d{1,3}\\.\\-?\\d{1,3}", end(schemaElement)))));
	}
	
	final static void makeWeakReferenceTypeElement(
			Element schemaElement,
			TypeDefinitionWeakObjectReference weakRefType,
			String targetPrefix) {
		
		schemaElement.appendChild(
				simpleType(weakRefType.getSymbol(), 
						restriction(targetPrefix, "TargetType", end(schemaElement))));
	}
	
	final static void makeStrongReferenceTypeElement(
			Element schemaElement,
			TypeDefinitionStrongObjectReference strongRefType,
			String targetPrefix,
			Map<ClassDefinition, Set<ClassDefinition>> hierarchy) {
		
		Set<ClassDefinition> descendents = getAllDescendents(strongRefType.getObjectType(), hierarchy);
		descendents.add(strongRefType.getObjectType());
		Element choice = choice(end(schemaElement));
		
		for ( ClassDefinition child : descendents ) {
			if (child.isConcrete())
				choice.appendChild(elementRef(targetPrefix, child.getSymbol(), end(schemaElement)));
		}
		
		schemaElement.appendChild(
				complexType(strongRefType.getSymbol(), choice));
	}
	
	final static void makeSetTypeElement(
			Element schemaElement,
			TypeDefinitionSet setRefType,
			String targetPrefix,
			Map<ClassDefinition, Set<ClassDefinition>> hierarchy) {
		
		if (setRefType.getElementType() instanceof TypeDefinitionStrongObjectReference) {
			Set<ClassDefinition> descendents = getAllDescendents(
					((TypeDefinitionStrongObjectReference) setRefType.getElementType()).getObjectType(), hierarchy);
			descendents.add(((TypeDefinitionStrongObjectReference) setRefType.getElementType()).getObjectType());
			
			Element choice = choice(end(schemaElement));
			XMLBuilder.setAttribute(choice, "", "", "minOccurs", "0");
			XMLBuilder.setAttribute(choice, "", "", "maxOccurs", "unbounded");
			
			for ( ClassDefinition child : descendents ) {
				if (child.isConcrete())
					choice.appendChild(elementRef(targetPrefix, child.getSymbol(), end(schemaElement)));
			}
			
			schemaElement.appendChild(
					complexType(setRefType.getSymbol(), choice));
			
			return;
		}
		
		schemaElement.appendChild(
				complexType(setRefType.getSymbol(), 
						sequence(
								listRefElement(targetPrefix, setRefType.getElementType().getSymbol(), 
										end(schemaElement)))));
	}
	
	final static void makeVariableArrayTypeElement(
			Element schemaElement,
			TypeDefinitionVariableArray variableArrayType,
			String targetPrefix,
			Map<ClassDefinition, Set<ClassDefinition>> hierarchy, 
			List<TypeDefinitionString> stringList) {
	
		switch (variableArrayType.getType().getTypeCategory()) {
		
		case StrongObjRef:
			makeVariableArrayStrongRef(schemaElement, variableArrayType, targetPrefix, hierarchy);
			return;
		case Character:
			makeVariableArrayStringArray(schemaElement, variableArrayType, targetPrefix, hierarchy, stringList);
			return;
		case Int:
			if (variableArrayType.getSymbol().equals("DataValue")) {
				makeVariableArrayDataValue(schemaElement, variableArrayType, targetPrefix, hierarchy);
				return;
			}
			if (variableArrayType.getSymbol().contains("StringArray")) {
				makeVariableArrayStringArray(schemaElement, variableArrayType, targetPrefix, hierarchy, stringList);
				return;
			}
			break;
		default:
			break;
		}
			
		schemaElement.appendChild(
				complexType(variableArrayType.getSymbol(), 
						sequence(
								listRefElement(targetPrefix, variableArrayType.getType().getSymbol(), 
										end(schemaElement)))));		
	}
	
	final static void makeVariableArrayStrongRef(
			Element schemaElement,
			TypeDefinitionVariableArray variableArrayType,
			String targetPrefix,
			Map<ClassDefinition, Set<ClassDefinition>> hierarchy) {
		
		Set<ClassDefinition> descendents = getAllDescendents(
				((TypeDefinitionStrongObjectReference) variableArrayType.getType()).getObjectType(), hierarchy);
		descendents.add(((TypeDefinitionStrongObjectReference) variableArrayType.getType()).getObjectType());
		
		Element choice = choice(end(schemaElement));
		XMLBuilder.setAttribute(choice, "", "", "minOccurs", "0");
		XMLBuilder.setAttribute(choice, "", "", "maxOccurs", "unbounded");
		
		for ( ClassDefinition child : descendents ) {
			if (child.isConcrete())
				choice.appendChild(elementRef(targetPrefix, child.getSymbol(), end(schemaElement)));
		}
		
		schemaElement.appendChild(
				complexType(variableArrayType.getSymbol(), choice));
	}
	
	final static void makeVariableArrayStringArray(
			Element schemaElement,
			TypeDefinitionVariableArray variableArrayType,
			String targetPrefix,
			Map<ClassDefinition, Set<ClassDefinition>> hierarchy,
			List<TypeDefinitionString> stringList) {
		
		TypeDefinition targetStringType = null;
		for ( TypeDefinitionString stringTypeItem : stringList )
			if (stringTypeItem.getElementType().equals(variableArrayType.getType())) {
				targetStringType = stringTypeItem;
				break;
			}
		
		if (targetStringType == null) targetStringType = variableArrayType.getType();
		
		schemaElement.appendChild(
				complexType(variableArrayType.getSymbol(), 
						sequence(
								listRefElement(targetPrefix, targetStringType.getSymbol(), end(schemaElement)))));
	}
	
	final static void makeVariableArrayDataValue(
			Element schemaElement,
			TypeDefinitionVariableArray variableArrayType,
			String targetPrefix,
			Map<ClassDefinition, Set<ClassDefinition>> hierarchy) {
		
		schemaElement.appendChild(
				simpleType("DataValue",
						restriction(targetPrefix, "HexByteArrayType", end(schemaElement))));
	}

	final static void makeRenameTypeElement(
			Element schemaElement,
			TypeDefinitionRename renameType,
			String targetPrefix) {
		
		Document document = schemaElement.getOwnerDocument();
		Element dummySchema = document.createElementNS(XSD_NAMESPACE, XSD_PREFIX + ":DUMMY"); 
		TypeDefinition baseType = renameType.getBaseType();
		
		switch (baseType.getTypeCategory()) {
		
		case Int:
			makeIntegerTypeElement(dummySchema, (TypeDefinitionInteger) baseType, targetPrefix);
			break;
		case Character:
			makeCharacterTypeElement(dummySchema, (TypeDefinitionCharacter) baseType, targetPrefix);
			break;
		case String:
			makeCharacterTypeElement(dummySchema, (TypeDefinitionString) baseType, targetPrefix);
			break;
		case Enum:
			makeEnumerationTypeElement(dummySchema, (TypeDefinitionEnumeration) baseType, targetPrefix);
			break;
		case ExtEnum:
			makeExtendibleEnumerationTypeElement(dummySchema, (TypeDefinitionExtendibleEnumeration) baseType, targetPrefix);
			break;
		case Indirect:
			makeIndirectTypeElement(dummySchema, (TypeDefinitionIndirect) baseType, targetPrefix);
			break;
		case Opaque:
			makeOpaqueTypeElement(dummySchema, (TypeDefinitionOpaque) baseType, targetPrefix);
			break;
		case Record:
			makeRecordTypeElement(dummySchema, (TypeDefinitionRecord) baseType, targetPrefix);
			break;
		case Rename:
			makeRenameTypeElement(dummySchema, (TypeDefinitionRename) baseType, targetPrefix);
			break;
		case Stream:
			makeStreamTypeElement(dummySchema, (TypeDefinitionStream) baseType, targetPrefix);
			break;
		case WeakObjRef:
			makeWeakReferenceTypeElement(dummySchema, (TypeDefinitionWeakObjectReference) baseType, targetPrefix);
			break;
		case StrongObjRef:
			makeStrongReferenceTypeElement(dummySchema, (TypeDefinitionStrongObjectReference) baseType, targetPrefix, 
					new HashMap<ClassDefinition, Set<ClassDefinition>>());
			break;
		case FixedArray:
			makeFixedArrayTypeElement(dummySchema, (TypeDefinitionFixedArray) baseType, targetPrefix, 
					new HashMap<ClassDefinition, Set<ClassDefinition>>());
			break;
		case VariableArray:
			makeVariableArrayTypeElement(dummySchema, (TypeDefinitionVariableArray) baseType, targetPrefix, 
					new HashMap<ClassDefinition, Set<ClassDefinition>>(), new ArrayList<TypeDefinitionString>());
			break;
		case Set:
			makeSetTypeElement(dummySchema, (TypeDefinitionSet) baseType, targetPrefix, 
					new HashMap<ClassDefinition, Set<ClassDefinition>>());
			break;
		default:
			return;
		}
		
		Element firstChild = (Element) dummySchema.getFirstChild();
		String referenceName = firstChild.getAttribute("name");
		if (firstChild.getNodeName().equals("xs:simpleType")) {
			schemaElement.appendChild(
					simpleType(renameType.getSymbol(),
							restriction(targetPrefix, referenceName, end(schemaElement))));
		}
		else {
			schemaElement.appendChild(
					complexType(renameType.getSymbol(),
							complexContent(
									extension(targetPrefix, referenceName, end(schemaElement)))));
		}
		
	}
	
	final static void makeReferenceElements(
			Element schemaElement, 
			List<TypeDefinitionVariableArray> variableArrayList, 
			List<TypeDefinitionFixedArray> fixedArrayList,
			List<TypeDefinitionSet> setList, 
			List<TypeDefinitionString> stringList,
			String targetPrefix) {
		
		Set<TypeDefinition> elementsOut = new HashSet<TypeDefinition>();
		
		Set<TypeDefinition> combinedSet = new HashSet<TypeDefinition>(
				variableArrayList.size() +
				fixedArrayList.size() +
				setList.size());
		combinedSet.addAll(variableArrayList);
		combinedSet.addAll(fixedArrayList);
		combinedSet.addAll(setList);
		
		for ( TypeDefinition type : combinedSet ) {
			
			TypeDefinition elementType = null;
			if (type instanceof TypeDefinitionSet) elementType = ((TypeDefinitionSet) type).getElementType();
			if (type instanceof TypeDefinitionVariableArray) elementType = ((TypeDefinitionVariableArray) type).getType();
			if (type instanceof TypeDefinitionFixedArray) elementType = ((TypeDefinitionFixedArray) type).getType();
			
			while (elementType instanceof TypeDefinitionRename)
				elementType = ((TypeDefinitionRename) elementType).getBaseType();
			
			switch (elementType.getTypeCategory()) {
			
			case Int:
			case Enum:
			case ExtEnum:
			case Record:
			case WeakObjRef:
				outputRefElement(elementType, schemaElement, elementsOut, targetPrefix);
				break;
			default:
				break;
			}
		}
		
		for ( TypeDefinitionSet type : setList ) {
			
			TypeDefinition elementType = type.getElementType();
			
			while (elementType instanceof TypeDefinitionRename)
				elementType = ((TypeDefinitionRename) elementType).getBaseType();

			switch (elementType.getTypeCategory()) {
			
			case Character:
			case Int:
				outputRefElement(elementType, schemaElement, elementsOut, targetPrefix);
				break;
			default:
				break;
			}
		}
		
		for ( TypeDefinitionString type : stringList ) {
			outputRefElement(type, schemaElement, elementsOut, targetPrefix);
		}
 	}
	
	final static void outputRefElement(
			TypeDefinition type,
			Element schemaElement,
			Set<TypeDefinition> elementsOut,
			String targetPrefix) {
		
		if (!elementsOut.contains(type)) {
			schemaElement.appendChild(
					element(type.getSymbol(), targetPrefix, type.getSymbol(), end(schemaElement)));
			elementsOut.add(type);
		}
	}

	/* --------------------------------------------------------------------------------------------------------
	 * XSD library beyond this point
	 */
	
	final static Element element(
			String name,
			Element child) {
		
		Document document = child.getOwnerDocument();
		Element element = document.createElementNS(XSD_NAMESPACE, XSD_PREFIX + ":element");
		XMLBuilder.setAttribute(element, "", "", "name", name);
		mother(element, child);
		return element;
	}
	
	final static Element element(
			String name,
			String targetPrefix,
			String type,
			Element child) {
		
		Document document = child.getOwnerDocument();
		Element element = document.createElementNS(XSD_NAMESPACE, XSD_PREFIX + ":element");
		XMLBuilder.setAttribute(element, "", "", "name", name);
		XMLBuilder.setAttribute(element, "", "", "type", targetPrefix + ":" + type);
		mother(element, child);
		return element;
		
	}
			
	final static Element optionalElement(
			String name,
			Element child) {
	
		Element element = element(name, child);
		XMLBuilder.setAttribute(element, "", "", "minOccurs", "0");
		return element;
	}
	
	final static Element optionalElementRef(
			String targetPrefix,
			String ref,
			Element child) {
		
		Element element = elementRef(targetPrefix, ref, child);
		XMLBuilder.setAttribute(element, "", "", "minOccurs", "0");
		return element;
	}

	final static Element abstractElement(
			String name,
			Element child) {
		
		Element element = element(name, child);
		XMLBuilder.setAttribute(element, "", "", "abstract", "true");
		return element;
	}
	
	final static Element listRefElement(
			String targetPrefix,
			String ref,
			Element child) {
	
		Document document = child.getOwnerDocument();
		Element element = document.createElementNS(XSD_NAMESPACE, XSD_PREFIX + ":element");
		XMLBuilder.setAttribute(element, "", "", "ref", targetPrefix + ":" + ref);
		XMLBuilder.setAttribute(element, "", "", "minOccurs", "0");
		XMLBuilder.setAttribute(element, "", "", "maxOccurs", "unbounded");
		return element;
	}
	
	final static Element elementRef(
			String targetPrefix,
			String ref,
			Element child) {
		
		Document document = child.getOwnerDocument();
		Element element = document.createElementNS(XSD_NAMESPACE, XSD_PREFIX + ":element");
		XMLBuilder.setAttribute(element, "", "", "ref", targetPrefix + ":" + ref);
		return element;		
	}
	
	final static Element complexType(
			Element... children) {
		
		Document document = children[0].getOwnerDocument();
		Element sequence =  document.createElementNS(XSD_NAMESPACE, XSD_PREFIX + ":complexType");
		
		for ( Element element : children )
			mother(sequence, element);
		
		return sequence;
	}
	
	final static Element complexType(
			String name,
			Element... children) {
		
		Element complexType = complexType(children);
		XMLBuilder.setAttribute(complexType, "", "", "name", name);
		return complexType;
	}
	
	final static Element sequence(
			Element... children) {
		
		Document document = children[0].getOwnerDocument();
		Element sequence =  document.createElementNS(XSD_NAMESPACE, XSD_PREFIX + ":sequence");
		
		for ( Element element : children )
			mother(sequence, element);
		
		return sequence;
	}
	
	final static Element choice(
			Element... children) {
		
		Document document = children[0].getOwnerDocument();
		Element sequence =  document.createElementNS(XSD_NAMESPACE, XSD_PREFIX + ":choice");
		
		for ( Element element : children )
			mother(sequence, element);
		
		return sequence;
	}

	final static Element end(
			Element bluff) {
		
		Document document = bluff.getOwnerDocument();
		return document.createElement("BLUFF");
	}
	
	final static Element mother(
			Element mother,
			Element child) {
		
		if (!child.getNodeName().equals("BLUFF"))
			mother.appendChild(child);
		
		return mother;
	}
	
	final static Element requiredAttribute(
			String name,
			String targetPrefix,
			String type,
			Element child) {
		
		Document document = child.getOwnerDocument();
		Element attributeElement = document.createElementNS(XSD_NAMESPACE, XSD_PREFIX + ":attribute");
		XMLBuilder.setAttribute(attributeElement, "", "", "name", name);
		XMLBuilder.setAttribute(attributeElement, "", "", "type", targetPrefix + ":" + type);
		XMLBuilder.setAttribute(attributeElement, "", "", "use", "required");
		return mother(attributeElement, child);
	}

	final static Element attribute(
			String name,
			String targetPrefix,
			String type,
			Element child) {
		
		Document document = child.getOwnerDocument();
		Element attributeElement = document.createElementNS(XSD_NAMESPACE, XSD_PREFIX + ":attribute");
		XMLBuilder.setAttribute(attributeElement, "", "", "name", name);
		XMLBuilder.setAttribute(attributeElement, "", "", "type", targetPrefix + ":" + type);
		return mother(attributeElement, child);
	}
	
	final static Element attributeRef(
			String targetPrefix,
			String ref,
			Element child) {
		
		Document document = child.getOwnerDocument();
		Element attributeElement = document.createElementNS(XSD_NAMESPACE, XSD_PREFIX + ":attribute");
		XMLBuilder.setAttribute(attributeElement, "", "", "ref", targetPrefix + ":" + ref);
		return mother(attributeElement, child);
	}
			
	final static Element attributeRefRequired(
			String targetPrefix,
			String ref,
			Element child) {
		
		Element attributeElement = attributeRef(targetPrefix, ref, child);
		XMLBuilder.setAttribute(attributeElement, "", "", "use", "required");
		return attributeElement;
	}

	final static Element attributeRefOptional(
			String targetPrefix,
			String ref,
			Element child) {
		
		Element attributeElement = attributeRef(targetPrefix, ref, child);
		XMLBuilder.setAttribute(attributeElement, "", "", "use", "optional");
		return attributeElement;
	}
	
	final static Element simpleType(
			String name,
			Element child) {
		
		Element element = simpleType(child);
		XMLBuilder.setAttribute(element, "", "", "name", name);
		return element;
	}
	
	final static Element simpleType(
			Element child) {
		
		Document document = child.getOwnerDocument();
		return mother(document.createElementNS(XSD_NAMESPACE, XSD_PREFIX + ":simpleType"), child);
	}
	
	final static Element union(
			String memberTypes,
			Element... children) {

		Element union = union(children);
		XMLBuilder.setAttribute(union, "", "", "memberTypes", memberTypes);
		return union;
	}
		
	final static Element union(
			Element... children) {

		Document document = children[0].getOwnerDocument();
		Element union = document.createElementNS(XSD_NAMESPACE, XSD_PREFIX + ":union");
		
		for ( Element element : children )
			mother(union, element);
		
		return union;
	}
	
	final static Element restriction(
			String targetPrefix,
			String base,
			Element... children) {
		
		Document document = children[0].getOwnerDocument();
		Element restriction = document.createElementNS(XSD_NAMESPACE, XSD_PREFIX + ":restriction");
		
		for ( Element child : children )
			mother(restriction, child);
		
		XMLBuilder.setAttribute(restriction, "", "", "base", targetPrefix + ":" + base);
		return restriction;
	}
	
	final static Element extension(
			String targetPrefix,
			String base,
			Element... children) {
		
		Document document = children[0].getOwnerDocument();
		Element restriction = document.createElementNS(XSD_NAMESPACE, XSD_PREFIX + ":extension");
		
		for ( Element child : children )
			mother(restriction, child);
		
		XMLBuilder.setAttribute(restriction, "", "", "base", targetPrefix + ":" + base);
		return restriction;
	}

	final static Element pattern(
			String value,
			Element child) {

		Document document = child.getOwnerDocument();
		Element pattern = mother(document.createElementNS(XSD_NAMESPACE, XSD_PREFIX + ":pattern"), child);
		XMLBuilder.setAttribute(pattern, "", "", "value", value);
		return pattern;
	}
	
	final static Element enumeration(
			String value,
			Element child) {

		Document document = child.getOwnerDocument();
		Element pattern = mother(document.createElementNS(XSD_NAMESPACE, XSD_PREFIX + ":enumeration"), child);
		XMLBuilder.setAttribute(pattern, "", "", "value", value);
		return pattern;
	}
	
	final static Element complexContent(
			Element... children) {
		
		Document document = children[0].getOwnerDocument();
		Element complexContent = document.createElementNS(XSD_NAMESPACE, XSD_PREFIX + ":complexContent");
		
		for ( Element child : children )
			mother(complexContent, child);
	
		return complexContent;
	}
	
	final static Element mixedComplexContent(
			Element... children) {
		
		Element complexContent = complexContent(children);
		XMLBuilder.setAttribute(complexContent, "", "", "mixed", "true");
		return complexContent;
	}
	
	final static Element simpleContent(
			Element... children) {
		
		Document document = children[0].getOwnerDocument();
		Element simpleContent = document.createElementNS(XSD_NAMESPACE, XSD_PREFIX + ":simpleContent");
		
		for ( Element child : children )
			mother(simpleContent, child);
	
		return simpleContent;
	}
		
	/* -----------------------------------------------------------------------------------------
	 * Utility methods.
	 */
	
	final static String upperFirstLetter(
			String bumpMeUp) {
		
		if (bumpMeUp == null) return null;
		
		switch (bumpMeUp.length()) {
		case 0:
			return bumpMeUp;
		case 1:
			return Character.toString(Character.toUpperCase(bumpMeUp.charAt(0)));
		default:
			return Character.toUpperCase(bumpMeUp.charAt(0)) + bumpMeUp.substring(1);
		}
	}
	
	final static Map<ClassDefinition, Set<ClassDefinition>> buildTypeHierarchy(
			Collection<ClassDefinition> classes) {
		
		Map<ClassDefinition, Set<ClassDefinition>> hierarchy = 
			new HashMap<ClassDefinition, Set<ClassDefinition>>(classes.size());
		
		for ( ClassDefinition classItem : classes ) {
			
			if (!hierarchy.containsKey(classItem))
				hierarchy.put(classItem, new HashSet<ClassDefinition>());
			
			if (!classItem.isRoot()) {
				ClassDefinition parent = classItem.getParent();
				Set<ClassDefinition> children = hierarchy.get(parent);
				if (children == null) {
					children = new HashSet<ClassDefinition>();
					hierarchy.put(parent, children);
				}
				children.add(classItem);
			}
		}
		
		return hierarchy;
	}
  	
	final static Set<ClassDefinition> getAllDescendents(
			ClassDefinition classItem,
			Map<ClassDefinition, Set<ClassDefinition>> hierarchy) {
		
		Set<ClassDefinition> descendents = new HashSet<ClassDefinition>();
		if (hierarchy.containsKey(classItem)) {
			Set<ClassDefinition> children = hierarchy.get(classItem);
			for ( ClassDefinition child : children ) {
				descendents.add(child);
				descendents.addAll(getAllDescendents(child, hierarchy));
			}
		}
		
		return descendents;
	}
	
	public final static void main(
			String args[]) {
		
		System.out.println(generateBaselineXSDAsAString());
	}
	
}
