// 
// System.Xml.Serialization.XmlSchemaImporter
//
// Author:
//   Tim Coleman (tim@timcoleman.com)
//   Lluis Sanchez Gual (lluis@ximian.com)
//
// Copyright (C) Tim Coleman, 2002
//

//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System.Xml;
using System.Xml.Schema;
using System.Collections;

namespace System.Xml.Serialization {
#if CONFIG_SERIALIZATION
	public class XmlSchemaImporter {

		#region Fields

		XmlSchemas schemas;
		CodeIdentifiers typeIdentifiers;
		CodeIdentifiers elemIdentifiers = new CodeIdentifiers ();
		Hashtable mappedTypes = new Hashtable ();
		Hashtable dataMappedTypes = new Hashtable ();
		Queue pendingMaps = new Queue ();
		Hashtable sharedAnonymousTypes = new Hashtable ();
		bool encodedFormat = false;
		XmlReflectionImporter auxXmlRefImporter;
		SoapReflectionImporter auxSoapRefImporter;

		static readonly XmlQualifiedName anyType = new XmlQualifiedName ("anyType",XmlSchema.Namespace);
		static readonly XmlQualifiedName arrayType = new XmlQualifiedName ("Array",XmlSerializer.EncodingNamespace);
		static readonly XmlQualifiedName arrayTypeRefName = new XmlQualifiedName ("arrayType",XmlSerializer.EncodingNamespace);
		
		const string XmlNamespace = "http://www.w3.org/XML/1998/namespace";
		
		XmlSchemaElement anyElement = null;

		class MapFixup
		{
			public XmlTypeMapping Map;
			public XmlSchemaComplexType SchemaType;
			public XmlQualifiedName TypeName;
		}

		#endregion

		#region Constructors

		public XmlSchemaImporter (XmlSchemas schemas)
		{
			this.schemas = schemas;
			typeIdentifiers = new CodeIdentifiers ();
		}

		public XmlSchemaImporter (XmlSchemas schemas, CodeIdentifiers typeIdentifiers)
			: this (schemas)
		{
			this.typeIdentifiers = typeIdentifiers;
		}
		
		internal bool UseEncodedFormat
		{
			get { return encodedFormat; }
			set { encodedFormat = value; }
		}

		#endregion // Constructors

		#region Methods

		public XmlMembersMapping ImportAnyType (XmlQualifiedName typeName, string elementName)
		{
			if (typeName == XmlQualifiedName.Empty)
			{
				XmlTypeMapMemberAnyElement mapMem = new XmlTypeMapMemberAnyElement ();
				mapMem.Name = typeName.Name;
				mapMem.TypeData = TypeTranslator.GetTypeData(typeof(XmlNode));
				mapMem.ElementInfo.Add (CreateElementInfo (typeName.Namespace, mapMem, typeName.Name, mapMem.TypeData, true, XmlSchemaForm.None));
				
				XmlMemberMapping[] mm = new XmlMemberMapping [1];
				mm[0] = new XmlMemberMapping (typeName.Name, typeName.Namespace, mapMem, encodedFormat);
				return new XmlMembersMapping (mm);
			}
			else
			{
				XmlSchemaComplexType stype = (XmlSchemaComplexType) schemas.Find (typeName, typeof (XmlSchemaComplexType));
				if (stype == null) 
					throw new InvalidOperationException ("Referenced type '" + typeName + "' not found");
				
				if (!CanBeAnyElement (stype))
					throw new InvalidOperationException ("The type '" + typeName + "' is not valid for a collection of any elements");
					
				ClassMap cmap = new ClassMap ();
				CodeIdentifiers classIds = new CodeIdentifiers ();
				bool isMixed = stype.IsMixed;
				ImportSequenceContent (typeName, cmap, ((XmlSchemaSequence) stype.Particle).Items, classIds, false, ref isMixed);
				XmlTypeMapMemberAnyElement mapMem = (XmlTypeMapMemberAnyElement) cmap.AllMembers[0];
				mapMem.Name = typeName.Name;
				
				XmlMemberMapping[] mm = new XmlMemberMapping [1];
				mm[0] = new XmlMemberMapping (typeName.Name, typeName.Namespace, mapMem, encodedFormat);
				return new XmlMembersMapping (mm);
			}
		}

		public XmlTypeMapping ImportDerivedTypeMapping (XmlQualifiedName name, Type baseType)
		{
			return ImportDerivedTypeMapping (name, baseType, true);
		}
		
		public XmlTypeMapping ImportDerivedTypeMapping (XmlQualifiedName name, Type baseType, bool baseTypeCanBeIndirect)
		{
			XmlQualifiedName qname;
			XmlSchemaType stype;
			
			if (encodedFormat)
			{
				qname = name;
				stype = schemas.Find (name, typeof (XmlSchemaComplexType)) as XmlSchemaComplexType;
				if (stype == null) throw new InvalidOperationException ("Schema type '" + name + "' not found or not valid");
			}
			else
			{
				if (!LocateElement (name, out qname, out stype) || stype == null)
					return null;
			}
			
			XmlTypeMapping map = GetRegisteredTypeMapping (qname);
			if (map != null)
			{
				// If the type has already been imported, make sure that the map 
				// has the requested base type
				
				SetMapBaseType (map, baseType);
				map.UpdateRoot (name);
				return map;
			}
			
			map = CreateTypeMapping (qname, SchemaTypes.Class, name);
			map.Documentation = GetDocumentation (stype);
			RegisterMapFixup (map, qname, (XmlSchemaComplexType)stype);
			
			BuildPendingMaps ();
			SetMapBaseType (map, baseType);
			
			return map;
		}
		
		void SetMapBaseType (XmlTypeMapping map, Type baseType)
		{
			// This method sets the base type for a given map.
			// If the map already inherits from this type, it does nothing.
			
			// Fiirst of all, check if the map already inherits from baseType
				
			XmlTypeMapping topMap = null;
			while (map != null)
			{
				if (map.TypeData.Type == baseType)
					return;
				topMap = map;
				map = map.BaseMap;
			}
			
			// Does not have the requested base type.
			// Then, get/create a map for that base type.
			
			XmlTypeMapping baseMap = ReflectType (baseType, null);
			
			// Add this map as a derived map of the base map

			topMap.BaseMap = baseMap;
			baseMap.DerivedTypes.Add (topMap);
			baseMap.DerivedTypes.AddRange (topMap.DerivedTypes);
			
			// Now add the base type fields to all derived maps

			ClassMap baseClassMap = (ClassMap)baseMap.ObjectMap;
			
			ClassMap cmap = (ClassMap)topMap.ObjectMap;
			foreach (XmlTypeMapMember member in baseClassMap.AllMembers)
				cmap.AddMember (member);
				
			foreach (XmlTypeMapping derivedMap in topMap.DerivedTypes)
			{
				cmap = (ClassMap)derivedMap.ObjectMap;
				foreach (XmlTypeMapMember member in baseClassMap.AllMembers)
					cmap.AddMember (member);
			}
		}

		public XmlMembersMapping ImportMembersMapping (XmlQualifiedName name)
		{
			XmlSchemaElement elem = (XmlSchemaElement) schemas.Find (name, typeof (XmlSchemaElement));
			if (elem == null) throw new InvalidOperationException ("Schema element '" + name + "' not found or not valid");

			XmlSchemaComplexType stype;
			if (elem.SchemaType != null)
			{
				stype = elem.SchemaType as XmlSchemaComplexType;
			}
			else
			{
				if (elem.SchemaTypeName.IsEmpty) return null;
				if (elem.SchemaTypeName.Namespace == XmlSchema.Namespace) return null;
				object type = schemas.Find (elem.SchemaTypeName, typeof (XmlSchemaComplexType));
				if (type == null) throw new InvalidOperationException ("Schema type '" + elem.SchemaTypeName + "' not found");
				stype = type as XmlSchemaComplexType;
			}
			
			if (stype == null) 
				throw new InvalidOperationException ("Schema element '" + name + "' not found or not valid");
			
			XmlMemberMapping[] mapping = ImportMembersMappingComposite (stype, name);			
			return new XmlMembersMapping (name.Name, name.Namespace, mapping);
		}
		
		public XmlMembersMapping ImportMembersMapping (XmlQualifiedName[] names)
		{
			XmlMemberMapping[] mapping = new XmlMemberMapping [names.Length];
			for (int n=0; n<names.Length; n++)
			{
				XmlSchemaElement elem = (XmlSchemaElement) schemas.Find (names[n], typeof (XmlSchemaElement));
				if (elem == null) throw new InvalidOperationException ("Schema element '" + names[n] + "' not found");
				
				XmlQualifiedName typeQName = new XmlQualifiedName ("Message", names[n].Namespace);
				XmlTypeMapping tmap;
				TypeData td = GetElementTypeData (typeQName, elem, names[n], out tmap);
				
				mapping[n] = ImportMemberMapping (elem.Name, typeQName.Namespace, elem.IsNillable, td, tmap);
			}
			BuildPendingMaps ();
			return new XmlMembersMapping (mapping);
		}
		
		internal XmlMembersMapping ImportEncodedMembersMapping (string name, string ns, SoapSchemaMember[] members, bool hasWrapperElement)
		{
			XmlMemberMapping[] mapping = new XmlMemberMapping [members.Length];
			for (int n=0; n<members.Length; n++)
			{
				TypeData td = GetTypeData (members[n].MemberType, null);
				XmlTypeMapping tmap = GetTypeMapping (td);
				mapping[n] = ImportMemberMapping (members[n].MemberName, members[n].MemberType.Namespace, true, td, tmap);
			}
			BuildPendingMaps ();
			return new XmlMembersMapping (name, ns, hasWrapperElement, false, mapping);
		}
		
		internal XmlMembersMapping ImportEncodedMembersMapping (string name, string ns, SoapSchemaMember member)
		{
			XmlSchemaComplexType stype = schemas.Find (member.MemberType, typeof (XmlSchemaComplexType)) as XmlSchemaComplexType;
			if (stype == null) throw new InvalidOperationException ("Schema type '" + member.MemberType + "' not found or not valid");

			XmlMemberMapping[] mapping = ImportMembersMappingComposite (stype, member.MemberType);			
			return new XmlMembersMapping (name, ns, mapping);
		}
		
		XmlMemberMapping[] ImportMembersMappingComposite (XmlSchemaComplexType stype, XmlQualifiedName refer)
		{
			if (stype.Particle == null) 
				return new XmlMemberMapping [0];

			ClassMap cmap = new ClassMap ();
			
			XmlSchemaSequence seq = stype.Particle as XmlSchemaSequence;
			if (seq == null) throw new InvalidOperationException ("Schema element '" + refer + "' cannot be imported as XmlMembersMapping");

			CodeIdentifiers classIds = new CodeIdentifiers ();
			ImportParticleComplexContent (refer, cmap, seq, classIds, false);
			ImportAttributes (refer, cmap, stype.Attributes, stype.AnyAttribute, classIds);

			BuildPendingMaps ();

			int n = 0;
			XmlMemberMapping[] mapping = new XmlMemberMapping [cmap.AllMembers.Count];
			foreach (XmlTypeMapMember mapMem in cmap.AllMembers)
				mapping[n++] = new XmlMemberMapping (mapMem.Name, refer.Namespace, mapMem, encodedFormat);
				
			return mapping;
		}
		
		XmlMemberMapping ImportMemberMapping (string name, string ns, bool isNullable, TypeData type, XmlTypeMapping emap)
		{
			XmlTypeMapMemberElement mapMem;
			
			if (type.IsListType)
				mapMem = new XmlTypeMapMemberList ();
			else
				mapMem = new XmlTypeMapMemberElement ();
			
			mapMem.Name = name;
			mapMem.TypeData = type;
			mapMem.ElementInfo.Add (CreateElementInfo (ns, mapMem, name, type, isNullable, XmlSchemaForm.None, emap));
			return new XmlMemberMapping (name, ns, mapMem, encodedFormat);
		}
		
		[MonoTODO]
		public XmlMembersMapping ImportMembersMapping (XmlQualifiedName[] names, Type baseType, bool baseTypeCanBeIndirect)
		{
			throw new NotImplementedException ();
		}

		public XmlTypeMapping ImportTypeMapping (XmlQualifiedName name)
		{
			XmlQualifiedName qname;
			XmlSchemaType stype;
			if (!LocateElement (name, out qname, out stype)) return null;
			
			if (stype == null) {
				// Importing a primitive type
				TypeData td = TypeTranslator.GetPrimitiveTypeData (qname.Name);
				return ReflectType (td.Type, name.Namespace);
			}
			
			XmlTypeMapping map = GetRegisteredTypeMapping (qname);
			if (map != null) return map;
			
			map = CreateTypeMapping (qname, SchemaTypes.Class, name);
			map.Documentation = GetDocumentation (stype);
			RegisterMapFixup (map, qname, (XmlSchemaComplexType)stype);
			
			BuildPendingMaps ();
			return map;
		}

		bool LocateElement (XmlQualifiedName name, out XmlQualifiedName qname, out XmlSchemaType stype)
		{
			qname = null;
			stype = null;
			
			XmlSchemaElement elem = (XmlSchemaElement) schemas.Find (name, typeof (XmlSchemaElement));
			if (elem == null) return false;

			// The root element must be an element with complex type

			if (elem.SchemaType != null)
			{
				stype = elem.SchemaType;
				qname = name;
			}
			else
			{
				if (elem.SchemaTypeName.IsEmpty) return false;
				
				if (elem.SchemaTypeName.Namespace == XmlSchema.Namespace) {
					qname = elem.SchemaTypeName;
					return true;
				}
				
				object type = schemas.Find (elem.SchemaTypeName, typeof (XmlSchemaComplexType));
				if (type == null) type = schemas.Find (elem.SchemaTypeName, typeof (XmlSchemaSimpleType));
				if (type == null) throw new InvalidOperationException ("Schema type '" + elem.SchemaTypeName + "' not found");
				stype = (XmlSchemaType) type;
				qname = stype.QualifiedName;
				
				XmlSchemaType btype = stype.BaseSchemaType as XmlSchemaType;
				if (btype != null && btype.QualifiedName == elem.SchemaTypeName)
					throw new InvalidOperationException ("Cannot import schema for type '" + elem.SchemaTypeName.Name + "' from namespace '" + elem.SchemaTypeName.Namespace + "'. Redefine not supported");
			}

			if (stype is XmlSchemaSimpleType) return false;
			return true;
		}

		XmlTypeMapping ImportType (XmlQualifiedName name, XmlQualifiedName root)
		{
			XmlTypeMapping map = GetRegisteredTypeMapping (name);
			if (map != null) {
				map.UpdateRoot (root);
				return map;
			}

			XmlSchemaType type = (XmlSchemaType) schemas.Find (name, typeof (XmlSchemaComplexType));
			if (type == null) type = (XmlSchemaType) schemas.Find (name, typeof (XmlSchemaSimpleType));
			
			if (type == null) 
			{
				if (name.Namespace == XmlSerializer.EncodingNamespace)
					throw new InvalidOperationException ("Referenced type '" + name + "' valid only for encoded SOAP");
				else
					throw new InvalidOperationException ("Referenced type '" + name + "' not found");
			}

			return ImportType (name, type, root);
		}

		XmlTypeMapping ImportType (XmlQualifiedName name, XmlSchemaType stype, XmlQualifiedName root)
		{
			XmlTypeMapping map = GetRegisteredTypeMapping (name);
			if (map != null) {
				XmlSchemaComplexType ct = stype as XmlSchemaComplexType;
				if (map.TypeData.SchemaType != SchemaTypes.Class || ct == null || !CanBeArray (name, ct)) {
					map.UpdateRoot (root);
					return map;
				}
					
				// The map was initially imported as a class, but it turns out that it is an
				// array. It has to be imported now as array.
			}
			
			if (stype is XmlSchemaComplexType)
				return ImportClassComplexType (name, (XmlSchemaComplexType) stype, root);
			else if (stype is XmlSchemaSimpleType)
				return ImportClassSimpleType (name, (XmlSchemaSimpleType) stype, root);

			throw new NotSupportedException ("Schema type not supported: " + stype.GetType ());
		}

		XmlTypeMapping ImportClassComplexType (XmlQualifiedName typeQName, XmlSchemaComplexType stype, XmlQualifiedName root)
		{
			XmlTypeMapping map;

			// The need for fixups: If the complex type is an array, then to get the type of the
			// array we need first to get the type of the items of the array.
			// But if one of the item types or its children has a referece to this type array,
			// then we enter in an infinite loop. This does not happen with class types because
			// the class map is registered before parsing the children. We can't do the same
			// with the array type because to register the array map we need the type of the array.

			Type anyType = GetAnyElementType (stype);
			if (anyType != null)
				return GetTypeMapping (TypeTranslator.GetTypeData(anyType));
				
			if (CanBeArray (typeQName, stype))
			{
				TypeData typeData;
				ListMap listMap = BuildArrayMap (typeQName, stype, out typeData);
				if (listMap != null)
				{
					map = CreateArrayTypeMapping (typeQName, typeData);
					map.ObjectMap = listMap;
					return map;
				}

				// After all, it is not an array. Create a class map then.
			}
			else if (CanBeIXmlSerializable (stype))
			{
				return ImportXmlSerializableMapping (typeQName.Namespace);
			}

			// Register the map right now but do not build it,
			// This will avoid loops.

			map = CreateTypeMapping (typeQName, SchemaTypes.Class, root);
			map.Documentation = GetDocumentation (stype);
			RegisterMapFixup (map, typeQName, stype);
			return map;
		}

		void RegisterMapFixup (XmlTypeMapping map, XmlQualifiedName typeQName, XmlSchemaComplexType stype)
		{
			MapFixup fixup = new MapFixup ();
			fixup.Map = map;
			fixup.SchemaType = stype;
			fixup.TypeName = typeQName;
			pendingMaps.Enqueue (fixup);
		}

		void BuildPendingMaps ()
		{
			while (pendingMaps.Count > 0) {
				MapFixup fixup  = (MapFixup) pendingMaps.Dequeue ();
				if (fixup.Map.ObjectMap == null) {
					BuildClassMap (fixup.Map, fixup.TypeName, fixup.SchemaType);
					if (fixup.Map.ObjectMap == null) pendingMaps.Enqueue (fixup);
				}
			}
		}

		void BuildPendingMap (XmlTypeMapping map)
		{
			if (map.ObjectMap != null) return;

			foreach (MapFixup fixup in pendingMaps)
			{
				if (fixup.Map == map) {
					BuildClassMap (fixup.Map, fixup.TypeName, fixup.SchemaType);
					return;
				}
			}
			throw new InvalidOperationException ("Can't complete map of type " + map.XmlType + " : " + map.Namespace);
		}

		void BuildClassMap (XmlTypeMapping map, XmlQualifiedName typeQName, XmlSchemaComplexType stype)
		{
			CodeIdentifiers classIds = new CodeIdentifiers();
			classIds.AddReserved (map.TypeData.TypeName);

			ClassMap cmap = new ClassMap ();
			map.ObjectMap = cmap;
			bool isMixed = stype.IsMixed;

			if (stype.Particle != null)
				ImportParticleComplexContent (typeQName, cmap, stype.Particle, classIds, isMixed);
			else
			{
				if (stype.ContentModel is XmlSchemaSimpleContent) {
					ImportSimpleContent (typeQName, map, (XmlSchemaSimpleContent)stype.ContentModel, classIds, isMixed);
				}
				else if (stype.ContentModel is XmlSchemaComplexContent) {
					ImportComplexContent (typeQName, map, (XmlSchemaComplexContent)stype.ContentModel, classIds, isMixed);
				}
			}

			ImportAttributes (typeQName, cmap, stype.Attributes, stype.AnyAttribute, classIds);
			ImportExtensionTypes (typeQName);
			
			GetTypeMapping (TypeTranslator.GetTypeData (typeof(object))).DerivedTypes.Add (map);
		}
		
		void ImportAttributes (XmlQualifiedName typeQName, ClassMap cmap, XmlSchemaObjectCollection atts, XmlSchemaAnyAttribute anyat, CodeIdentifiers classIds)
		{
			if (anyat != null)
			{
    			XmlTypeMapMemberAnyAttribute member = new XmlTypeMapMemberAnyAttribute ();
				member.Name = classIds.AddUnique ("AnyAttribute", member);
				member.TypeData = TypeTranslator.GetTypeData (typeof(XmlAttribute[]));
				cmap.AddMember (member);
			}
			
			foreach (XmlSchemaObject at in atts)
			{
				if (at is XmlSchemaAttribute)
				{
					string ns;
					XmlSchemaAttribute attr = (XmlSchemaAttribute)at;
					XmlSchemaAttribute refAttr = GetRefAttribute (typeQName, attr, out ns);
					XmlTypeMapMemberAttribute member = new XmlTypeMapMemberAttribute ();
					member.Name = classIds.AddUnique (CodeIdentifier.MakeValid (refAttr.Name), member);
					member.Documentation = GetDocumentation (attr);
					member.AttributeName = refAttr.Name;
					member.Namespace = ns;
					member.Form = refAttr.Form;
					member.TypeData = GetAttributeTypeData (typeQName, attr);
					
					if (refAttr.DefaultValue != null) 
						member.DefaultValue = XmlCustomFormatter.FromXmlString (member.TypeData, refAttr.DefaultValue);
					else if (member.TypeData.IsValueType)
						member.IsOptionalValueType = true;//(refAttr.ValidatedUse != XmlSchemaUse.Required);
						
					if (member.TypeData.IsComplexType)
						member.MappedType = GetTypeMapping (member.TypeData);
					cmap.AddMember (member);
				}
				else if (at is XmlSchemaAttributeGroupRef)
				{
					XmlSchemaAttributeGroupRef gref = (XmlSchemaAttributeGroupRef)at;
					XmlSchemaAttributeGroup grp = FindRefAttributeGroup (gref.RefName);
					ImportAttributes (typeQName, cmap, grp.Attributes, grp.AnyAttribute, classIds);
				}
			}
		}

		ListMap BuildArrayMap (XmlQualifiedName typeQName, XmlSchemaComplexType stype, out TypeData arrayTypeData)
		{
			if (encodedFormat)
			{
				XmlSchemaComplexContent content = stype.ContentModel as XmlSchemaComplexContent;
				XmlSchemaComplexContentRestriction rest = content.Content as XmlSchemaComplexContentRestriction;
				XmlSchemaAttribute arrayTypeAt = FindArrayAttribute (rest.Attributes);
				
				if (arrayTypeAt != null)
				{
					XmlAttribute[] uatts = arrayTypeAt.UnhandledAttributes;
					if (uatts == null || uatts.Length == 0) throw new InvalidOperationException ("arrayType attribute not specified in array declaration: " + typeQName);
					
					XmlAttribute xat = null;
					foreach (XmlAttribute at in uatts)
						if (at.LocalName == "arrayType" && at.NamespaceURI == XmlSerializer.WsdlNamespace)
							{ xat = at; break; }
					
					if (xat == null) 
						throw new InvalidOperationException ("arrayType attribute not specified in array declaration: " + typeQName);
	
					string name, ns, dims;
					TypeTranslator.ParseArrayType (xat.Value, out name, out ns, out dims);
					return BuildEncodedArrayMap (name + dims, ns, out arrayTypeData);
				}
				else
				{
					XmlSchemaElement elem = null;
					XmlSchemaSequence seq = rest.Particle as XmlSchemaSequence;
					if (seq != null && seq.Items.Count == 1) 
						elem = seq.Items[0] as XmlSchemaElement;
					else {
						XmlSchemaAll all = rest.Particle as XmlSchemaAll;
						if (all != null && all.Items.Count == 1)
							elem = all.Items[0] as XmlSchemaElement;
					}
					if (elem == null)
						throw new InvalidOperationException ("Unknown array format");
						
					return BuildEncodedArrayMap (elem.SchemaTypeName.Name + "[]", elem.SchemaTypeName.Namespace, out arrayTypeData);
				}
			}
			else
			{
				ClassMap cmap = new ClassMap ();
				CodeIdentifiers classIds = new CodeIdentifiers();
				ImportParticleComplexContent (typeQName, cmap, stype.Particle, classIds, stype.IsMixed);

				XmlTypeMapMemberFlatList list = (cmap.AllMembers.Count == 1) ? cmap.AllMembers[0] as XmlTypeMapMemberFlatList : null;
				if (list != null && list.ChoiceMember == null)
				{
					arrayTypeData = list.TypeData;
					return list.ListMap;
				}
				else
				{
					arrayTypeData = null;
					return null;
				}
			}
		}
		
		ListMap BuildEncodedArrayMap (string type, string ns, out TypeData arrayTypeData)
		{
			ListMap map = new ListMap ();
			
			int i = type.LastIndexOf ("[");
			if (i == -1) throw new InvalidOperationException ("Invalid arrayType value: " + type);
			if (type.IndexOf (",",i) != -1) throw new InvalidOperationException ("Multidimensional arrays are not supported");
			
			string itemType = type.Substring (0,i);
			
			TypeData itemTypeData;
			if (itemType.IndexOf ("[") != -1) 
			{
				ListMap innerListMap = BuildEncodedArrayMap (itemType, ns, out itemTypeData);
				
				int dims = itemType.Split ('[').Length - 1;
				string name = TypeTranslator.GetArrayName (type, dims);
				XmlQualifiedName qname = new XmlQualifiedName (name, ns);
				XmlTypeMapping tmap = CreateArrayTypeMapping (qname, itemTypeData);
				tmap.ObjectMap = innerListMap;
			}
			else
			{
				itemTypeData = GetTypeData (new XmlQualifiedName (itemType, ns), null);
			}
			
			arrayTypeData = itemTypeData.ListTypeData;
			
			map.ItemInfo = new XmlTypeMapElementInfoList();
			map.ItemInfo.Add (CreateElementInfo ("", null, "Item", itemTypeData, true, XmlSchemaForm.None));
			return map;
		}
		
		XmlSchemaAttribute FindArrayAttribute (XmlSchemaObjectCollection atts)
		{
			foreach (object ob in atts)
			{
				XmlSchemaAttribute att = ob as XmlSchemaAttribute;
				if (att != null && att.RefName == arrayTypeRefName) return att;
				
				XmlSchemaAttributeGroupRef gref = ob as XmlSchemaAttributeGroupRef;
				if (gref != null)
				{
					XmlSchemaAttributeGroup grp = FindRefAttributeGroup (gref.RefName);
					att = FindArrayAttribute (grp.Attributes);
					if (att != null) return att;
				}
			}
			return null;
		}

		void ImportParticleComplexContent (XmlQualifiedName typeQName, ClassMap cmap, XmlSchemaParticle particle, CodeIdentifiers classIds, bool isMixed)
		{
			ImportParticleContent (typeQName, cmap, particle, classIds, false, ref isMixed);
			if (isMixed && cmap.XmlTextCollector == null)
			{
				XmlTypeMapMemberFlatList member = new XmlTypeMapMemberFlatList ();
				member.Name = classIds.AddUnique ("Text", member);
				member.TypeData = TypeTranslator.GetTypeData (typeof(string[]));
				member.ElementInfo.Add (CreateTextElementInfo (typeQName.Namespace, member, member.TypeData.ListItemTypeData));
				member.IsXmlTextCollector = true;
				member.ListMap = new ListMap ();
				member.ListMap.ItemInfo = member.ElementInfo;
				cmap.AddMember (member);
			}
		}
		
		void ImportParticleContent (XmlQualifiedName typeQName, ClassMap cmap, XmlSchemaParticle particle, CodeIdentifiers classIds, bool multiValue, ref bool isMixed)
		{
			if (particle is XmlSchemaGroupRef)
				particle = GetRefGroupParticle ((XmlSchemaGroupRef)particle);

			if (particle.MaxOccurs > 1) multiValue = true;
			
			if (particle is XmlSchemaSequence) {
				ImportSequenceContent (typeQName, cmap, ((XmlSchemaSequence)particle).Items, classIds, multiValue, ref isMixed);
			}
			else if (particle is XmlSchemaChoice) {
				if (((XmlSchemaChoice)particle).Items.Count == 1)
					ImportSequenceContent (typeQName, cmap, ((XmlSchemaChoice)particle).Items, classIds, multiValue, ref isMixed);
				else
					ImportChoiceContent (typeQName, cmap, (XmlSchemaChoice)particle, classIds, multiValue);
			}
			else if (particle is XmlSchemaAll) {
				ImportSequenceContent (typeQName, cmap, ((XmlSchemaAll)particle).Items, classIds, multiValue, ref isMixed);
			}
		}

		void ImportSequenceContent (XmlQualifiedName typeQName, ClassMap cmap, XmlSchemaObjectCollection items, CodeIdentifiers classIds, bool multiValue, ref bool isMixed)
		{
			foreach (XmlSchemaObject item in items)
			{
				if (item is XmlSchemaElement)
				{
					string ns;
					XmlSchemaElement elem = (XmlSchemaElement) item;
					XmlTypeMapping emap;
					TypeData typeData = GetElementTypeData (typeQName, elem, null, out emap);
					XmlSchemaElement refElem = GetRefElement (typeQName, elem, out ns);

					if (elem.MaxOccurs == 1 && !multiValue)
					{
						XmlTypeMapMemberElement member = null;
						if (typeData.SchemaType != SchemaTypes.Array)
						{
							member = new XmlTypeMapMemberElement ();
							if (refElem.DefaultValue != null) member.DefaultValue = XmlCustomFormatter.FromXmlString (typeData, refElem.DefaultValue);
						}
						else if (GetTypeMapping (typeData).IsSimpleType)
						{
							// It is a simple list (space separated list).
							// Since this is not supported, map as a single item value
							// TODO: improve this
							member = new XmlTypeMapMemberElement ();
							typeData = typeData.ListItemTypeData;
						}
						else
							member = new XmlTypeMapMemberList ();

						if (elem.MinOccurs == 0 && typeData.IsValueType)
							member.IsOptionalValueType = true;

						member.Name = classIds.AddUnique(CodeIdentifier.MakeValid(refElem.Name), member);
						member.Documentation = GetDocumentation (elem);
						member.TypeData = typeData;
						member.ElementInfo.Add (CreateElementInfo (ns, member, refElem.Name, typeData, refElem.IsNillable, refElem.Form, emap));
						cmap.AddMember (member);
					}
					else
					{
						XmlTypeMapMemberFlatList member = new XmlTypeMapMemberFlatList ();
						member.ListMap = new ListMap ();
						member.Name = classIds.AddUnique(CodeIdentifier.MakeValid(refElem.Name), member);
						member.Documentation = GetDocumentation (elem);
						member.TypeData = typeData.ListTypeData;
						member.ElementInfo.Add (CreateElementInfo (ns, member, refElem.Name, typeData, refElem.IsNillable, refElem.Form, emap));
						member.ListMap.ItemInfo = member.ElementInfo;
						cmap.AddMember (member);
					}
				}
				else if (item is XmlSchemaAny)
				{
					XmlSchemaAny elem = (XmlSchemaAny) item;
					XmlTypeMapMemberAnyElement member = new XmlTypeMapMemberAnyElement ();
					member.Name = classIds.AddUnique ("Any", member);
					member.Documentation = GetDocumentation (elem);
					
					Type ctype;
					if (elem.MaxOccurs != 1 || multiValue)
						ctype = isMixed ? typeof(XmlNode[]) : typeof(XmlElement[]);
					else
						ctype = isMixed ? typeof(XmlNode) : typeof(XmlElement);

					member.TypeData = TypeTranslator.GetTypeData (ctype);
					XmlTypeMapElementInfo einfo = new XmlTypeMapElementInfo (member, member.TypeData);
					einfo.IsUnnamedAnyElement = true;
					member.ElementInfo.Add (einfo);

					if (isMixed)
					{
						einfo = CreateTextElementInfo (typeQName.Namespace, member, member.TypeData);
						member.ElementInfo.Add (einfo);
						member.IsXmlTextCollector = true;
						isMixed = false;	//Allow only one XmlTextAttribute
					}
					
					cmap.AddMember (member);
				}
				else if (item is XmlSchemaParticle) {
					ImportParticleContent (typeQName, cmap, (XmlSchemaParticle)item, classIds, multiValue, ref isMixed);
				}
			}
		}
		
		void ImportChoiceContent (XmlQualifiedName typeQName, ClassMap cmap, XmlSchemaChoice choice, CodeIdentifiers classIds, bool multiValue)
		{
			XmlTypeMapElementInfoList choices = new XmlTypeMapElementInfoList ();
			multiValue = ImportChoices (typeQName, null, choices, choice.Items) || multiValue;
			if (choices.Count == 0) return;

			if (choice.MaxOccurs > 1) multiValue = true;

			XmlTypeMapMemberElement member;
			if (multiValue)
			{
				member = new XmlTypeMapMemberFlatList ();
				member.Name = classIds.AddUnique ("Items", member);
				ListMap listMap = new ListMap ();
				listMap.ItemInfo = choices;
				((XmlTypeMapMemberFlatList)member).ListMap = listMap;
			}
			else
			{
				member = new XmlTypeMapMemberElement ();
				member.Name = classIds.AddUnique ("Item", member);
			}
			
			// If all choices have the same type, use that type for the member.
			// If not use System.Object.
			// If there are at least two choices with the same type, use a choice
			// identifier attribute

			TypeData typeData = null;
			bool twoEqual = false;
			bool allEqual = true;
			Hashtable types = new Hashtable ();

			foreach (XmlTypeMapElementInfo einfo in choices)
			{
				if (types.ContainsKey (einfo.TypeData)) twoEqual = true;
				else types.Add (einfo.TypeData, einfo);

				TypeData choiceType = einfo.TypeData;
				if (choiceType.SchemaType == SchemaTypes.Class)
				{
					// When comparing class types, use the most generic class in the
					// inheritance hierarchy

					XmlTypeMapping choiceMap = GetTypeMapping (choiceType);
					BuildPendingMap (choiceMap);
					while (choiceMap.BaseMap != null) {
						choiceMap = choiceMap.BaseMap;
						BuildPendingMap (choiceMap);
						choiceType = choiceMap.TypeData;
					}
				}
				
				if (typeData == null) typeData = choiceType;
				else if (typeData != choiceType) allEqual = false;
			}

			if (!allEqual)
				typeData = TypeTranslator.GetTypeData (typeof(object));

			if (twoEqual)
			{
				// Create the choice member
				XmlTypeMapMemberElement choiceMember = new XmlTypeMapMemberElement ();
				choiceMember.Ignore = true;
				choiceMember.Name = classIds.AddUnique (member.Name + "ElementName", choiceMember);
				member.ChoiceMember = choiceMember.Name;

				// Create the choice enum
				XmlTypeMapping enumMap = CreateTypeMapping (new XmlQualifiedName (member.Name + "ChoiceType", typeQName.Namespace), SchemaTypes.Enum, null);
				enumMap.IncludeInSchema = false;

				CodeIdentifiers codeIdents = new CodeIdentifiers ();
				EnumMap.EnumMapMember[] members = new EnumMap.EnumMapMember [choices.Count];
				for (int n=0; n<choices.Count; n++)
				{
					XmlTypeMapElementInfo it =(XmlTypeMapElementInfo) choices[n];
					bool extraNs = (it.Namespace != null && it.Namespace != "" && it.Namespace != typeQName.Namespace);
					string xmlName = extraNs ? it.Namespace + ":" + it.ElementName : it.ElementName;
					string enumName = codeIdents.AddUnique (CodeIdentifier.MakeValid (it.ElementName), it);
					members [n] = new EnumMap.EnumMapMember (xmlName, enumName);
				}
				enumMap.ObjectMap = new EnumMap (members, false);

				choiceMember.TypeData = multiValue ? enumMap.TypeData.ListTypeData : enumMap.TypeData;
				choiceMember.ElementInfo.Add (CreateElementInfo (typeQName.Namespace, choiceMember, choiceMember.Name, choiceMember.TypeData, false, XmlSchemaForm.None));
				cmap.AddMember (choiceMember);
			}

			if (multiValue)
				typeData = typeData.ListTypeData;

			member.ElementInfo = choices;
			member.Documentation = GetDocumentation (choice);
			member.TypeData = typeData;
			cmap.AddMember (member);
		}

		bool ImportChoices (XmlQualifiedName typeQName, XmlTypeMapMember member, XmlTypeMapElementInfoList choices, XmlSchemaObjectCollection items)
		{
			bool multiValue = false;
			foreach (XmlSchemaObject titem in items)
			{
				XmlSchemaObject item = titem;
				if (item is XmlSchemaGroupRef)
					item = GetRefGroupParticle ((XmlSchemaGroupRef)item);

				if (item is XmlSchemaElement)
				{
					string ns;
					XmlSchemaElement elem = (XmlSchemaElement) item;
					XmlTypeMapping emap;
					TypeData typeData = GetElementTypeData (typeQName, elem, null, out emap);
					XmlSchemaElement refElem = GetRefElement (typeQName, elem, out ns);
					choices.Add (CreateElementInfo (ns, member, refElem.Name, typeData, refElem.IsNillable, refElem.Form, emap));
					if (elem.MaxOccurs > 1) multiValue = true;
				}
				else if (item is XmlSchemaAny)
				{
					XmlTypeMapElementInfo einfo = new XmlTypeMapElementInfo (member, TypeTranslator.GetTypeData(typeof(XmlElement)));
					einfo.IsUnnamedAnyElement = true;
					choices.Add (einfo);
				}
				else if (item is XmlSchemaChoice) {
					multiValue = ImportChoices (typeQName, member, choices, ((XmlSchemaChoice)item).Items) || multiValue;
				}
				else if (item is XmlSchemaSequence) {
					multiValue = ImportChoices (typeQName, member, choices, ((XmlSchemaSequence)item).Items) || multiValue;
				}
			}
			return multiValue;
		}

		void ImportSimpleContent (XmlQualifiedName typeQName, XmlTypeMapping map, XmlSchemaSimpleContent content, CodeIdentifiers classIds, bool isMixed)
		{
			ClassMap cmap = (ClassMap)map.ObjectMap;
			
			XmlQualifiedName qname = GetContentBaseType (content.Content);

			XmlTypeMapMemberElement member = new XmlTypeMapMemberElement ();
			member.Name = classIds.AddUnique("Value", member);
			member.TypeData = FindBuiltInType (qname);
			member.ElementInfo.Add (CreateTextElementInfo (typeQName.Namespace, member, member.TypeData));
			member.IsXmlTextCollector = true;
			cmap.AddMember (member);

			XmlSchemaSimpleContentExtension ext = content.Content as XmlSchemaSimpleContentExtension;
			if (ext != null)
				ImportAttributes (typeQName, cmap, ext.Attributes, ext.AnyAttribute, classIds);
		}

		TypeData FindBuiltInType (XmlQualifiedName qname)
		{
			if (qname.Namespace == XmlSchema.Namespace)
				return TypeTranslator.GetPrimitiveTypeData (qname.Name);

			XmlSchemaComplexType ct = (XmlSchemaComplexType) schemas.Find (qname, typeof(XmlSchemaComplexType));
			if (ct != null)
			{
				XmlSchemaSimpleContent sc = ct.ContentModel as XmlSchemaSimpleContent;
				if (sc == null) throw new InvalidOperationException ("Invalid schema");
				return FindBuiltInType (GetContentBaseType (sc.Content));
			}
			
			XmlSchemaSimpleType st = (XmlSchemaSimpleType) schemas.Find (qname, typeof(XmlSchemaSimpleType));
			if (st != null)
				return FindBuiltInType (qname, st);

			throw new InvalidOperationException ("Definition of type " + qname + " not found");
		}

		TypeData FindBuiltInType (XmlQualifiedName qname, XmlSchemaSimpleType st)
		{
			if (CanBeEnum (st))
				return ImportType (qname, null).TypeData;

			if (st.Content is XmlSchemaSimpleTypeRestriction) {
				return FindBuiltInType (GetContentBaseType (st.Content));
			}
			else if (st.Content is XmlSchemaSimpleTypeList) {
				return FindBuiltInType (GetContentBaseType (st.Content)).ListTypeData;
			}
			else if (st.Content is XmlSchemaSimpleTypeUnion)
			{
				// Check if all types of the union are equal. If not, then will use anyType.
				XmlSchemaSimpleTypeUnion uni = (XmlSchemaSimpleTypeUnion) st.Content;
				TypeData utype = null;

				// Anonymous types are unique
				if (uni.BaseTypes.Count != 0 && uni.MemberTypes.Length != 0)
					return FindBuiltInType (anyType);

				foreach (XmlQualifiedName mt in uni.MemberTypes)
				{
					TypeData qn = FindBuiltInType (mt);
					if (utype != null && qn != utype) return FindBuiltInType (anyType);
					else utype = qn;
				}
				return utype;
			}
			else
				return null;
		}

		XmlQualifiedName GetContentBaseType (XmlSchemaObject ob)
		{
			if (ob is XmlSchemaSimpleContentExtension)
				return ((XmlSchemaSimpleContentExtension)ob).BaseTypeName;
			else if (ob is XmlSchemaSimpleContentRestriction)
				return ((XmlSchemaSimpleContentRestriction)ob).BaseTypeName;
			else if (ob is XmlSchemaSimpleTypeRestriction)
				return ((XmlSchemaSimpleTypeRestriction)ob).BaseTypeName;
			else if (ob is XmlSchemaSimpleTypeList)
				return ((XmlSchemaSimpleTypeList)ob).ItemTypeName;
			else
				return null;
		}

		void ImportComplexContent (XmlQualifiedName typeQName, XmlTypeMapping map, XmlSchemaComplexContent content, CodeIdentifiers classIds, bool isMixed)
		{
			ClassMap cmap = (ClassMap)map.ObjectMap;
			XmlQualifiedName qname;

			XmlSchemaComplexContentExtension ext = content.Content as XmlSchemaComplexContentExtension;
			if (ext != null) qname = ext.BaseTypeName;
			else qname = ((XmlSchemaComplexContentRestriction)content.Content).BaseTypeName;
			
			if (qname == typeQName)
				throw new InvalidOperationException ("Cannot import schema for type '" + typeQName.Name + "' from namespace '" + typeQName.Namespace + "'. Redefine not supported");
			
			// Add base map members to this map

			XmlTypeMapping baseMap = ImportType (qname, null);
			BuildPendingMap (baseMap);
			ClassMap baseClassMap = (ClassMap)baseMap.ObjectMap;

			foreach (XmlTypeMapMember member in baseClassMap.AllMembers)
				cmap.AddMember (member);

			if (baseClassMap.XmlTextCollector != null) isMixed = false;
			else if (content.IsMixed) isMixed = true;

			map.BaseMap = baseMap;
			baseMap.DerivedTypes.Add (map);

			if (ext != null) {
				// Add the members of this map
				if (ext.Particle != null)
					ImportParticleComplexContent (typeQName, cmap, ext.Particle, classIds, isMixed);

				ImportAttributes (typeQName, cmap, ext.Attributes, ext.AnyAttribute, classIds);
			}
			else {
				if (isMixed) ImportParticleComplexContent (typeQName, cmap, null, classIds, true);
			}
		}
		
		void ImportExtensionTypes (XmlQualifiedName qname)
		{
			foreach (XmlSchema schema in schemas) {
				foreach (XmlSchemaObject sob in schema.Items) 
				{
					XmlSchemaComplexType sct = sob as XmlSchemaComplexType;
					if (sct != null && sct.ContentModel is XmlSchemaComplexContent) {
						XmlQualifiedName exqname;
						XmlSchemaComplexContentExtension ext = sct.ContentModel.Content as XmlSchemaComplexContentExtension;
						if (ext != null) exqname = ext.BaseTypeName;
						else exqname = ((XmlSchemaComplexContentRestriction)sct.ContentModel.Content).BaseTypeName;
						if (exqname == qname)
							ImportType (new XmlQualifiedName (sct.Name, schema.TargetNamespace), sct, null);
					}
				}
			}					
		}

		XmlTypeMapping ImportClassSimpleType (XmlQualifiedName typeQName, XmlSchemaSimpleType stype, XmlQualifiedName root)
		{
			if (CanBeEnum (stype))
			{
				// Create an enum map

				CodeIdentifiers codeIdents = new CodeIdentifiers ();
				XmlTypeMapping enumMap = CreateTypeMapping (typeQName, SchemaTypes.Enum, null);
				enumMap.Documentation = GetDocumentation (stype);
				
				bool isFlags = false;
				if (stype.Content is XmlSchemaSimpleTypeList) {
					stype = ((XmlSchemaSimpleTypeList)stype.Content).ItemType;
					isFlags = true;
				}
				XmlSchemaSimpleTypeRestriction rest = (XmlSchemaSimpleTypeRestriction)stype.Content;

				codeIdents.AddReserved (enumMap.TypeData.TypeName);

				EnumMap.EnumMapMember[] members = new EnumMap.EnumMapMember [rest.Facets.Count];
				for (int n=0; n<rest.Facets.Count; n++)
				{
					XmlSchemaEnumerationFacet enu = (XmlSchemaEnumerationFacet) rest.Facets[n];
					string enumName = codeIdents.AddUnique(CodeIdentifier.MakeValid (enu.Value), enu);
					members [n] = new EnumMap.EnumMapMember (enu.Value, enumName);
					members [n].Documentation = GetDocumentation (enu);
				}
				enumMap.ObjectMap = new EnumMap (members, isFlags);
				enumMap.IsSimpleType = true;
				return enumMap;
			}

			if (stype.Content is XmlSchemaSimpleTypeList)
			{
				XmlSchemaSimpleTypeList slist = (XmlSchemaSimpleTypeList)stype.Content;
				TypeData arrayTypeData = FindBuiltInType (slist.ItemTypeName, stype);

				ListMap listMap = new ListMap ();

				listMap.ItemInfo = new XmlTypeMapElementInfoList ();
				listMap.ItemInfo.Add (CreateElementInfo (typeQName.Namespace, null, "Item", arrayTypeData.ListItemTypeData, false, XmlSchemaForm.None));

				XmlTypeMapping map = CreateArrayTypeMapping (typeQName, arrayTypeData);
				map.ObjectMap = listMap;
				map.IsSimpleType = true;
				return map;
			}

			// It is an extension of a primitive or known type
			
			TypeData typeData = FindBuiltInType (typeQName, stype);
			return GetTypeMapping (typeData);
		}

		bool CanBeEnum (XmlSchemaSimpleType stype)
		{
			if (stype.Content is XmlSchemaSimpleTypeRestriction)
			{
				XmlSchemaSimpleTypeRestriction rest = (XmlSchemaSimpleTypeRestriction)stype.Content;
				if (rest.Facets.Count == 0) return false;
				foreach (object ob in rest.Facets)
					if (!(ob is XmlSchemaEnumerationFacet)) return false;
				return true;
			}
			else if (stype.Content is XmlSchemaSimpleTypeList)
			{
				XmlSchemaSimpleTypeList list = (XmlSchemaSimpleTypeList) stype.Content;
				return (list.ItemType != null && CanBeEnum (list.ItemType));
			}
			return false;
		}

		bool CanBeArray (XmlQualifiedName typeQName, XmlSchemaComplexType stype)
		{
			if (encodedFormat)
			{
				XmlSchemaComplexContent content = stype.ContentModel as XmlSchemaComplexContent;
				if (content == null) return false;
				XmlSchemaComplexContentRestriction rest = content.Content as XmlSchemaComplexContentRestriction;
				if (rest == null) return false;
				return rest.BaseTypeName == arrayType;
			}
			else
			{
				if (stype.Attributes.Count > 0 || stype.AnyAttribute != null) return false;
				else return !stype.IsMixed && CanBeArray (typeQName, stype.Particle, false);
			}
		}

		bool CanBeArray (XmlQualifiedName typeQName, XmlSchemaParticle particle, bool multiValue)
		{
			// To be an array, there can't be a direct child of type typeQName

			if (particle == null) return false;

			multiValue = multiValue || particle.MaxOccurs > 1;

			if (particle is XmlSchemaGroupRef)
				return CanBeArray (typeQName, GetRefGroupParticle ((XmlSchemaGroupRef)particle), multiValue);

			if (particle is XmlSchemaElement)
			{
				XmlSchemaElement elem = (XmlSchemaElement)particle;
				if (!elem.RefName.IsEmpty)
					return CanBeArray (typeQName, FindRefElement (elem), multiValue);
				else
					return multiValue && !typeQName.Equals (((XmlSchemaElement)particle).SchemaTypeName);
			}

			if (particle is XmlSchemaAny)
				return multiValue;

			if (particle is XmlSchemaSequence)
			{
				XmlSchemaSequence seq = particle as XmlSchemaSequence;
				if (seq.Items.Count != 1) return false;
				return CanBeArray (typeQName, (XmlSchemaParticle)seq.Items[0], multiValue);
			}

			if (particle is XmlSchemaChoice)
			{
				// Can be array if all choices have different types
				ArrayList types = new ArrayList ();
				if(!CheckChoiceType (typeQName, particle, types, ref multiValue)) return false;
				return multiValue;
			}

			return false;
		}

		bool CheckChoiceType (XmlQualifiedName typeQName, XmlSchemaParticle particle, ArrayList types, ref bool multiValue)
		{
			XmlQualifiedName type = null;

			multiValue = multiValue || particle.MaxOccurs > 1;

			if (particle is XmlSchemaGroupRef)
				return CheckChoiceType (typeQName, GetRefGroupParticle ((XmlSchemaGroupRef)particle), types, ref multiValue);

			if (particle is XmlSchemaElement) {
				string ns;
				XmlSchemaElement elem = (XmlSchemaElement)particle;
				XmlSchemaElement refElem = GetRefElement (typeQName, elem, out ns);
				if (refElem.SchemaType != null) return true;
				type = refElem.SchemaTypeName;
			}
			else if (particle is XmlSchemaAny) {
				type = anyType;
			}
			else if (particle is XmlSchemaSequence)
			{
				XmlSchemaSequence seq = particle as XmlSchemaSequence;
				foreach (XmlSchemaParticle par in seq.Items)
					if (!CheckChoiceType (typeQName, par, types, ref multiValue)) return false;
				return true;
			}
			else if (particle is XmlSchemaChoice)
			{
				foreach (XmlSchemaParticle choice in ((XmlSchemaChoice)particle).Items)
					if (!CheckChoiceType (typeQName, choice, types, ref multiValue)) return false;
				return true;
			}

			if (typeQName.Equals (type)) return false;

			// For primitive types, compare using CLR types, since several
			// xml types can be mapped to a single CLR type

			string t;
			if (type.Namespace == XmlSchema.Namespace)
				t = TypeTranslator.GetPrimitiveTypeData (type.Name).FullTypeName + ":" + type.Namespace;

			else
				t = type.Name + ":" + type.Namespace;

			if (types.Contains (t)) return false;
			types.Add (t);
			return true;
		}
		
		bool CanBeAnyElement (XmlSchemaComplexType stype)
		{
			XmlSchemaSequence seq = stype.Particle as XmlSchemaSequence;
			return (seq != null) && (seq.Items.Count == 1) && (seq.Items[0] is XmlSchemaAny);
		}
		
		Type GetAnyElementType (XmlSchemaComplexType stype)
		{
			XmlSchemaSequence seq = stype.Particle as XmlSchemaSequence;
			
			if ((seq == null) || (seq.Items.Count != 1) || !(seq.Items[0] is XmlSchemaAny))
				return null;
			
			if (encodedFormat) 
				return typeof(object);

			XmlSchemaAny any = seq.Items[0] as XmlSchemaAny;
			if (any.MaxOccurs == 1)
			{
				if (stype.IsMixed)
					return typeof(XmlNode);
				else
					return typeof(XmlElement);
			}
			else
			{
				if (stype.IsMixed)
					return typeof(XmlNode[]);
				else
					return typeof(XmlElement[]);
			}
		}

		bool CanBeIXmlSerializable (XmlSchemaComplexType stype)
		{
			XmlSchemaSequence seq = stype.Particle as XmlSchemaSequence;
			if (seq == null) return false;
			if (seq.Items.Count != 2) return false;
			XmlSchemaElement elem = seq.Items[0] as XmlSchemaElement;
			if (elem == null) return false;
			if (elem.RefName != new XmlQualifiedName ("schema",XmlSchema.Namespace)) return false;
			return (seq.Items[1] is XmlSchemaAny);
		}
		
		XmlTypeMapping ImportXmlSerializableMapping (string ns)
		{
			XmlQualifiedName qname = new XmlQualifiedName ("System.Data.DataSet",ns);
			XmlTypeMapping map = mappedTypes [qname] as XmlTypeMapping;
			if (map != null) return map;
			
			TypeData typeData = new TypeData ("System.Data.DataSet", "System.Data.DataSet", "System.Data.DataSet", SchemaTypes.XmlSerializable, null);
			map = new XmlTypeMapping ("System.Data.DataSet", "", typeData, "System.Data.DataSet", ns);
			map.IncludeInSchema = true;
			mappedTypes [qname] = map;
			dataMappedTypes [typeData] = map;
			return map;
		}
		
		XmlTypeMapElementInfo CreateElementInfo (string ns, XmlTypeMapMember member, string name, TypeData typeData, bool isNillable, XmlSchemaForm form)
		{
			if (typeData.IsComplexType)
				return CreateElementInfo (ns, member, name, typeData, isNillable, form, GetTypeMapping (typeData));
			else
				return CreateElementInfo (ns, member, name, typeData, isNillable, form, null);
		}
		
		XmlTypeMapElementInfo CreateElementInfo (string ns, XmlTypeMapMember member, string name, TypeData typeData, bool isNillable, XmlSchemaForm form, XmlTypeMapping emap)
		{
			XmlTypeMapElementInfo einfo = new XmlTypeMapElementInfo (member, typeData);
			einfo.ElementName = name;
			einfo.Namespace = ns;
			einfo.IsNullable = isNillable;
			einfo.Form = form;
			if (typeData.IsComplexType)
				einfo.MappedType = emap;
			return einfo;
		}

		XmlTypeMapElementInfo CreateTextElementInfo (string ns, XmlTypeMapMember member, TypeData typeData)
		{
			XmlTypeMapElementInfo einfo = new XmlTypeMapElementInfo (member, typeData);
			einfo.IsTextElement = true;
			einfo.WrappedElement = false;
			if (typeData.IsComplexType)
				einfo.MappedType = GetTypeMapping (typeData);
			return einfo;
		}

		XmlTypeMapping CreateTypeMapping (XmlQualifiedName typeQName, SchemaTypes schemaType, XmlQualifiedName root)
		{
			string typeName = CodeIdentifier.MakeValid (typeQName.Name);
			typeName = typeIdentifiers.AddUnique (typeName, null);

			TypeData typeData = new TypeData (typeName, typeName, typeName, schemaType, null);

			string rootElem;
			string rootNs;
			if (root != null) {
				rootElem = root.Name;
				rootNs = root.Namespace;
			}
			else {
				rootElem = typeQName.Name;
				rootNs = "";
			}
			
			XmlTypeMapping map = new XmlTypeMapping (rootElem, rootNs, typeData, typeQName.Name, typeQName.Namespace);
			map.IncludeInSchema = true;
			mappedTypes [typeQName] = map;
			dataMappedTypes [typeData] = map;

			return map;
		}

		XmlTypeMapping CreateArrayTypeMapping (XmlQualifiedName typeQName, TypeData arrayTypeData)
		{
			XmlTypeMapping map;
			if (encodedFormat) map = new XmlTypeMapping ("Array", XmlSerializer.EncodingNamespace, arrayTypeData, "Array", XmlSerializer.EncodingNamespace);
			else map = new XmlTypeMapping (arrayTypeData.XmlType, typeQName.Namespace, arrayTypeData, arrayTypeData.XmlType, typeQName.Namespace);
			
			map.IncludeInSchema = true;
			mappedTypes [typeQName] = map;
			dataMappedTypes [arrayTypeData] = map;

			return map;
		}

		XmlSchemaElement GetRefElement (XmlQualifiedName typeQName, XmlSchemaElement elem, out string ns)
		{

			if (!elem.RefName.IsEmpty)
			{
				ns = elem.RefName.Namespace;
				return FindRefElement (elem);
			}
			else
			{
				ns = typeQName.Namespace;
				return elem;
			}
		}

		XmlSchemaAttribute GetRefAttribute (XmlQualifiedName typeQName, XmlSchemaAttribute attr, out string ns)
		{
			if (!attr.RefName.IsEmpty)
			{
				ns = attr.RefName.Namespace;
				XmlSchemaAttribute at = FindRefAttribute (attr.RefName);
				if (at == null) throw new InvalidOperationException ("The attribute " + attr.RefName + " is missing");
				return at;
			}
			else
			{
				ns = typeQName.Namespace;
				return attr;
			}
		}

		TypeData GetElementTypeData (XmlQualifiedName typeQName, XmlSchemaElement elem, XmlQualifiedName root, out XmlTypeMapping map)
		{
			bool sharedAnnType = false;
			map = null;
			
			if (!elem.RefName.IsEmpty) {
				XmlSchemaElement refElem = FindRefElement (elem);
				if (refElem == null) throw new InvalidOperationException ("Global element not found: " + elem.RefName);
				root = elem.RefName;
				elem = refElem;
				sharedAnnType = true;
			}

			TypeData td;
			if (!elem.SchemaTypeName.IsEmpty) {
				td = GetTypeData (elem.SchemaTypeName, root);
				map = GetRegisteredTypeMapping (elem.SchemaTypeName);
			}
			else if (elem.SchemaType == null) 
				td = TypeTranslator.GetTypeData (typeof(object));
			else 
				td = GetTypeData (elem.SchemaType, typeQName, elem.Name, sharedAnnType, root);
			
			if (map == null && td.IsComplexType)
				map = GetTypeMapping (td);
				
			return td;
		}

		TypeData GetAttributeTypeData (XmlQualifiedName typeQName, XmlSchemaAttribute attr)
		{
			bool sharedAnnType = false;

			if (!attr.RefName.IsEmpty) {
				XmlSchemaAttribute refAtt = FindRefAttribute (attr.RefName);
				if (refAtt == null) throw new InvalidOperationException ("Global attribute not found: " + attr.RefName);
				attr = refAtt;
				sharedAnnType = true;
			}
			
			if (!attr.SchemaTypeName.IsEmpty) return GetTypeData (attr.SchemaTypeName, null);
			if (attr.SchemaType == null) return TypeTranslator.GetTypeData (typeof(string));
			else return GetTypeData (attr.SchemaType, typeQName, attr.Name, sharedAnnType, null);
		}

		TypeData GetTypeData (XmlQualifiedName typeQName, XmlQualifiedName root)
		{
			if (typeQName.Namespace == XmlSchema.Namespace || (encodedFormat && typeQName.Namespace == ""))
				return TypeTranslator.GetPrimitiveTypeData (typeQName.Name);

			return ImportType (typeQName, root).TypeData;
		}

		TypeData GetTypeData (XmlSchemaType stype, XmlQualifiedName typeQNname, string propertyName, bool sharedAnnType, XmlQualifiedName root)
		{
			string baseName;

			if (sharedAnnType)
			{
				// Anonymous types defined in root elements or attributes can be shared among all elements that
				// reference this root element or attribute
				TypeData std = sharedAnonymousTypes [stype] as TypeData;
				if (std != null) return std;
				baseName = propertyName;
			}
			else
				baseName = typeQNname.Name + typeIdentifiers.MakeRightCase (propertyName);

			baseName = elemIdentifiers.AddUnique (baseName, stype);
			
			XmlQualifiedName newName;
			newName = new XmlQualifiedName (baseName, typeQNname.Namespace);

			XmlTypeMapping map = ImportType (newName, stype, root);
			if (sharedAnnType) sharedAnonymousTypes [stype] = map.TypeData;

			return map.TypeData;
		}

		XmlTypeMapping GetTypeMapping (TypeData typeData)
		{
			XmlTypeMapping map = (XmlTypeMapping) dataMappedTypes [typeData];
			if (map != null) return map;
			
			if (map == null && typeData.IsListType)
			{
				// Create an array map for the type

				XmlTypeMapping itemMap = GetTypeMapping (typeData.ListItemTypeData);
				
				map = new XmlTypeMapping (typeData.XmlType, itemMap.Namespace, typeData, typeData.XmlType, itemMap.Namespace);
				map.IncludeInSchema = true;

				ListMap listMap = new ListMap ();
				listMap.ItemInfo = new XmlTypeMapElementInfoList();
				listMap.ItemInfo.Add (CreateElementInfo (itemMap.Namespace, null, typeData.ListItemTypeData.XmlType, typeData.ListItemTypeData, false, XmlSchemaForm.None));
				map.ObjectMap = listMap;
				
				mappedTypes [new XmlQualifiedName(map.ElementName, map.Namespace)] = map;
				dataMappedTypes [typeData] = map;
				return map;
			}
			else if (typeData.SchemaType == SchemaTypes.Primitive || typeData.Type == typeof(object) || typeof(XmlNode).IsAssignableFrom(typeData.Type))
			{
				map = new XmlTypeMapping (typeData.XmlType, XmlSchema.Namespace, typeData, typeData.XmlType, XmlSchema.Namespace);
				map.IncludeInSchema = false;
				map.ObjectMap = new ClassMap ();
				dataMappedTypes [typeData] = map;
				
				if (typeData.Type == typeof(object))
				{
					// All complex types are subtypes of anyType, so all of them 
					// must also be imported
					
					foreach (XmlSchema schema in schemas) {
						foreach (XmlSchemaObject sob in schema.Items) 
						{
							XmlSchemaComplexType sct = sob as XmlSchemaComplexType;
							if (sct != null)
								ImportType (new XmlQualifiedName (sct.Name, schema.TargetNamespace), sct, null);
						}
					}					
				}
				
				return map;
			}
			
			throw new InvalidOperationException ("Map for type " + typeData.TypeName + " not found");
		}

		XmlTypeMapping GetRegisteredTypeMapping (XmlQualifiedName typeQName)
		{
			return (XmlTypeMapping) mappedTypes [typeQName];
		}

		XmlSchemaParticle GetRefGroupParticle (XmlSchemaGroupRef refGroup)
		{
			XmlSchemaGroup grp = (XmlSchemaGroup) schemas.Find (refGroup.RefName, typeof (XmlSchemaGroup));
			return grp.Particle;
		}

		XmlSchemaElement FindRefElement (XmlSchemaElement elem)
		{
			if (elem.RefName.Namespace == XmlSchema.Namespace)
			{
				if (anyElement != null) return anyElement;
				anyElement = new XmlSchemaElement ();
				anyElement.Name = "any";
				anyElement.SchemaTypeName = anyType;
				return anyElement;
			}
			return (XmlSchemaElement) schemas.Find (elem.RefName, typeof(XmlSchemaElement));
		}
		
		XmlSchemaAttribute FindRefAttribute (XmlQualifiedName refName)
		{
			if (refName.Namespace == XmlNamespace)
			{
				XmlSchemaAttribute at = new XmlSchemaAttribute ();
				at.Name = refName.Name;
				at.SchemaTypeName = new XmlQualifiedName ("string",XmlSchema.Namespace);
				return at;
			}
			return (XmlSchemaAttribute) schemas.Find (refName, typeof(XmlSchemaAttribute));
		}
		
		XmlSchemaAttributeGroup FindRefAttributeGroup (XmlQualifiedName refName)
		{
			XmlSchemaAttributeGroup grp = (XmlSchemaAttributeGroup) schemas.Find (refName, typeof(XmlSchemaAttributeGroup));
			foreach (XmlSchemaObject at in grp.Attributes)
			{
				if (at is XmlSchemaAttributeGroupRef && ((XmlSchemaAttributeGroupRef)at).RefName == refName)
					throw new InvalidOperationException ("Cannot import attribute group '" + refName.Name + "' from namespace '" + refName.Namespace + "'. Redefine not supported");
					
			}
			return grp;
		}
		
		XmlTypeMapping ReflectType (Type type, string ns)
		{
			if (!encodedFormat)
			{
				if (auxXmlRefImporter == null) auxXmlRefImporter = new XmlReflectionImporter ();
				return auxXmlRefImporter.ImportTypeMapping (type, ns);
			}
			else
			{
				if (auxSoapRefImporter == null) auxSoapRefImporter = new SoapReflectionImporter ();
				return auxSoapRefImporter.ImportTypeMapping (type, ns);
			}
		}


		string GetDocumentation (XmlSchemaAnnotated elem)
		{
			string res = "";
			XmlSchemaAnnotation anot = elem.Annotation;
			if (anot == null || anot.Items == null) return null;
			
			foreach (object ob in anot.Items)
			{
				XmlSchemaDocumentation doc = ob as XmlSchemaDocumentation;
				if (doc != null && doc.Markup != null && doc.Markup.Length > 0) {
					if (res != string.Empty) res += "\n";
					foreach (XmlNode node in doc.Markup)
						res += node.Value;
				}
			}
			return res;
		}

		#endregion // Methods
	}
#endif // CONFIG_SERIALIZATION
}
