// ReSharper disable once FunctionComplexityOverflow
        private TypeModel CreateTypeModel(Uri source, XmlSchemaAnnotated type, XmlQualifiedName qualifiedName)
        {
            TypeModel typeModel;
            if (!qualifiedName.IsEmpty && Types.TryGetValue(qualifiedName, out typeModel)) return typeModel;

            if (source == null)
                throw new ArgumentNullException("source");
            var namespaceModel = CreateNamespaceModel(source, qualifiedName);

            var docs = GetDocumentation(type);

            var group = type as XmlSchemaGroup;
            if (group != null)
            {
                var name = "I" + ToTitleCase(qualifiedName.Name);
                if (namespaceModel != null) name = namespaceModel.GetUniqueTypeName(name);

                var interfaceModel = new InterfaceModel(_configuration)
                {
                    Name = name,
                    Namespace = namespaceModel,
                    XmlSchemaName = qualifiedName
                };

                interfaceModel.Documentation.AddRange(docs);

                if (namespaceModel != null) namespaceModel.Types[name] = interfaceModel;
                if (!qualifiedName.IsEmpty) Types[qualifiedName] = interfaceModel;

                var particle = group.Particle;
                var items = GetElements(particle);
                var properties = CreatePropertiesForElements(source, interfaceModel, particle, items.Where(i => !(i.XmlParticle is XmlSchemaGroupRef)));
                interfaceModel.Properties.AddRange(properties);
                var interfaces = items.Select(i => i.XmlParticle).OfType<XmlSchemaGroupRef>()
                    .Select(i => (InterfaceModel)CreateTypeModel(new Uri(i.SourceUri), Groups[i.RefName], i.RefName));
                interfaceModel.Interfaces.AddRange(interfaces);

                return interfaceModel;
            }

            var attributeGroup = type as XmlSchemaAttributeGroup;
            if (attributeGroup != null)
            {
                var name = "I" + ToTitleCase(qualifiedName.Name);
                if (namespaceModel != null) name = namespaceModel.GetUniqueTypeName(name);

                var interfaceModel = new InterfaceModel(_configuration)
                {
                    Name = name,
                    Namespace = namespaceModel,
                    XmlSchemaName = qualifiedName
                };

                interfaceModel.Documentation.AddRange(docs);

                if (namespaceModel != null) namespaceModel.Types[name] = interfaceModel;
                if (!qualifiedName.IsEmpty) Types[qualifiedName] = interfaceModel;

                var items = attributeGroup.Attributes;
                var properties = CreatePropertiesForAttributes(source, interfaceModel, items.OfType<XmlSchemaAttribute>());
                interfaceModel.Properties.AddRange(properties);
                var interfaces = items.OfType<XmlSchemaAttributeGroupRef>()
                    .Select(a => (InterfaceModel)CreateTypeModel(new Uri(a.SourceUri), AttributeGroups[a.RefName], a.RefName));
                interfaceModel.Interfaces.AddRange(interfaces);

                return interfaceModel;
            }

            var complexType = type as XmlSchemaComplexType;
            if (complexType != null)
            {
                var name = ToTitleCase(qualifiedName.Name);
                if (namespaceModel != null) name = namespaceModel.GetUniqueTypeName(name);

                var classModel = new ClassModel(_configuration)
                {
                    Name = name,
                    Namespace = namespaceModel,
                    XmlSchemaName = qualifiedName,
                    XmlSchemaType = complexType,
                    IsAbstract = complexType.IsAbstract,
                    IsAnonymous = complexType.QualifiedName.Name == "",
                    IsMixed = complexType.IsMixed,
                    IsSubstitution = complexType.Parent is XmlSchemaElement && !((XmlSchemaElement)complexType.Parent).SubstitutionGroup.IsEmpty,
                    EnableDataBinding = EnableDataBinding,
                };

                classModel.Documentation.AddRange(docs);

                if (namespaceModel != null)
                {
                    namespaceModel.Types[classModel.Name] = classModel;
                }

                if (!qualifiedName.IsEmpty) Types[qualifiedName] = classModel;

                if (complexType.BaseXmlSchemaType != null && complexType.BaseXmlSchemaType.QualifiedName != AnyType)
                {
                    var baseModel = CreateTypeModel(source, complexType.BaseXmlSchemaType, complexType.BaseXmlSchemaType.QualifiedName);
                    classModel.BaseClass = baseModel;
                    if (baseModel is ClassModel) ((ClassModel)classModel.BaseClass).DerivedTypes.Add(classModel);
                }

                XmlSchemaParticle particle = null;
                if (classModel.BaseClass != null)
                {
                    if (complexType.ContentModel.Content is XmlSchemaComplexContentExtension)
                        particle = ((XmlSchemaComplexContentExtension)complexType.ContentModel.Content).Particle;

                    // If it's a restriction, do not duplicate elements on the derived class, they're already in the base class.
                    // See https://msdn.microsoft.com/en-us/library/f3z3wh0y.aspx
                    //else if (complexType.ContentModel.Content is XmlSchemaComplexContentRestriction)
                    //    particle = ((XmlSchemaComplexContentRestriction)complexType.ContentModel.Content).Particle;
                }
                else particle = complexType.ContentTypeParticle;

                var items = GetElements(particle);
                var properties = CreatePropertiesForElements(source, classModel, particle, items);
                classModel.Properties.AddRange(properties);

                if (GenerateInterfaces)
                {
                    var interfaces = items.Select(i => i.XmlParticle).OfType<XmlSchemaGroupRef>()
                        .Select(i => (InterfaceModel)CreateTypeModel(new Uri(i.SourceUri), Groups[i.RefName], i.RefName));
                    classModel.Interfaces.AddRange(interfaces);
                }

                XmlSchemaObjectCollection attributes = null;
                if (classModel.BaseClass != null)
                {
                    if (complexType.ContentModel.Content is XmlSchemaComplexContentExtension)
                        attributes = ((XmlSchemaComplexContentExtension)complexType.ContentModel.Content).Attributes;
                    else if (complexType.ContentModel.Content is XmlSchemaSimpleContentExtension)
                        attributes = ((XmlSchemaSimpleContentExtension)complexType.ContentModel.Content).Attributes;

                    // If it's a restriction, do not duplicate attributes on the derived class, they're already in the base class.
                    // See https://msdn.microsoft.com/en-us/library/f3z3wh0y.aspx
                    //else if (complexType.ContentModel.Content is XmlSchemaComplexContentRestriction)
                    //    attributes = ((XmlSchemaComplexContentRestriction)complexType.ContentModel.Content).Attributes;
                    //else if (complexType.ContentModel.Content is XmlSchemaSimpleContentRestriction)
                    //    attributes = ((XmlSchemaSimpleContentRestriction)complexType.ContentModel.Content).Attributes;
                }
                else attributes = complexType.Attributes;

                if (attributes != null)
                {
                    var attributeProperties = CreatePropertiesForAttributes(source, classModel, attributes.Cast<XmlSchemaObject>());
                    classModel.Properties.AddRange(attributeProperties);

                    if (GenerateInterfaces)
                    {
                        var attributeInterfaces = attributes.OfType<XmlSchemaAttributeGroupRef>()
                            .Select(i => (InterfaceModel)CreateTypeModel(new Uri(i.SourceUri), AttributeGroups[i.RefName], i.RefName));
                        classModel.Interfaces.AddRange(attributeInterfaces);
                    }
                }

                if (complexType.AnyAttribute != null)
                {
                    var property = new PropertyModel(_configuration)
                    {
                        OwningType = classModel,
                        Name = "AnyAttribute",
                        Type = new SimpleModel(_configuration) { ValueType = typeof(XmlAttribute), UseDataTypeAttribute = false },
                        IsAttribute = true,
                        IsCollection = true,
                        IsAny = true
                    };

                    var attributeDocs = GetDocumentation(complexType.AnyAttribute);
                    property.Documentation.AddRange(attributeDocs);

                    classModel.Properties.Add(property);
                }

                return classModel;
            }

            var simpleType = type as XmlSchemaSimpleType;
            if (simpleType != null)
            {
                var restrictions = new List<RestrictionModel>();

                var typeRestriction = simpleType.Content as XmlSchemaSimpleTypeRestriction;
                if (typeRestriction != null)
                {
                    var enumFacets = typeRestriction.Facets.OfType<XmlSchemaEnumerationFacet>().ToList();
                    var isEnum = (enumFacets.Count == typeRestriction.Facets.Count && enumFacets.Count != 0)
                                    || (EnumTypes.Contains(typeRestriction.BaseTypeName.Name) && enumFacets.Any());
                    if (isEnum)
                    {
                        // we got an enum
                        var name = ToTitleCase(qualifiedName.Name);
                        if (namespaceModel != null) name = namespaceModel.GetUniqueTypeName(name);

                        var enumModel = new EnumModel(_configuration)
                        {
                            Name = name,
                            Namespace = namespaceModel,
                            XmlSchemaName = qualifiedName,
                            XmlSchemaType = simpleType,
                        };

                        enumModel.Documentation.AddRange(docs);

                        foreach (var facet in enumFacets.DistinctBy(f => f.Value))
                        {
                            var value = new EnumValueModel
                            {
                                Name = ToTitleCase(facet.Value).ToNormalizedEnumName(),
                                Value = facet.Value
                            };

                            var valueDocs = GetDocumentation(facet);
                            value.Documentation.AddRange(valueDocs);

                            var deprecated = facet.Annotation != null && facet.Annotation.Items.OfType<XmlSchemaAppInfo>()
                                .Any(a => a.Markup.Any(m => m.Name == "annox:annotate" && m.HasChildNodes && m.FirstChild.Name == "jl:Deprecated"));
                            value.IsDeprecated = deprecated;

                            enumModel.Values.Add(value);
                        }

                        if (namespaceModel != null)
                        {
                            namespaceModel.Types[enumModel.Name] = enumModel;
                        }

                        if (!qualifiedName.IsEmpty) Types[qualifiedName] = enumModel;

                        return enumModel;
                    }

                    restrictions = GetRestrictions(typeRestriction.Facets.Cast<XmlSchemaFacet>(), simpleType).Where(r => r != null).Sanitize().ToList();
                }

                var simpleModelName = ToTitleCase(qualifiedName.Name);
                if (namespaceModel != null) simpleModelName = namespaceModel.GetUniqueTypeName(simpleModelName);

                var simpleModel = new SimpleModel(_configuration)
                {
                    Name = simpleModelName,
                    Namespace = namespaceModel,
                    XmlSchemaName = qualifiedName,
                    XmlSchemaType = simpleType,
                    ValueType = simpleType.Datatype.GetEffectiveType(_configuration),
                };

                simpleModel.Documentation.AddRange(docs);
                simpleModel.Restrictions.AddRange(restrictions);

                if (namespaceModel != null)
                {
                    namespaceModel.Types[simpleModel.Name] = simpleModel;
                }

                if (!qualifiedName.IsEmpty) Types[qualifiedName] = simpleModel;

                return simpleModel;
            }

            throw new Exception(string.Format("Cannot build declaration for {0}", qualifiedName));
        }
        private void BuildModel()
        {
            var objectModel = new SimpleModel(_configuration)
            {
                Name = "AnyType",
                Namespace = CreateNamespaceModel(new Uri(XmlSchema.Namespace), AnyType),
                XmlSchemaName = AnyType,
                XmlSchemaType = null,
                ValueType = typeof(object),
                UseDataTypeAttribute = false
            };

            Types[AnyType] = objectModel;

            AttributeGroups = Set.Schemas().Cast<XmlSchema>().SelectMany(s => s.AttributeGroups.Values.Cast<XmlSchemaAttributeGroup>()).ToDictionary(g => g.QualifiedName);
            Groups = Set.Schemas().Cast<XmlSchema>().SelectMany(s => s.Groups.Values.Cast<XmlSchemaGroup>()).ToDictionary(g => g.QualifiedName);

            foreach (var rootElement in Set.GlobalElements.Values.Cast<XmlSchemaElement>())
            {
                var source = new Uri(rootElement.GetSchema().SourceUri);
                var qualifiedName = rootElement.ElementSchemaType.QualifiedName;
                if (qualifiedName.IsEmpty) qualifiedName = rootElement.QualifiedName;
                var type = CreateTypeModel(source, rootElement.ElementSchemaType, qualifiedName);

                if (type.RootElementName != null)
                {
                    if (type is ClassModel)
                    {
                        // There is already another global element with this type.
                        // Need to create an empty derived class.

                        var derivedClassModel = new ClassModel(_configuration)
                        {
                            Name = ToTitleCase(rootElement.QualifiedName.Name),
                            Namespace = CreateNamespaceModel(source, rootElement.QualifiedName)
                        };

                        derivedClassModel.Documentation.AddRange(GetDocumentation(rootElement));

                        if (derivedClassModel.Namespace != null)
                        {
                            derivedClassModel.Name = derivedClassModel.Namespace.GetUniqueTypeName(derivedClassModel.Name);
                            derivedClassModel.Namespace.Types[derivedClassModel.Name] = derivedClassModel;
                        }

                        Types[rootElement.QualifiedName] = derivedClassModel;

                        derivedClassModel.BaseClass = (ClassModel)type;
                        ((ClassModel)derivedClassModel.BaseClass).DerivedTypes.Add(derivedClassModel);

                        derivedClassModel.RootElementName = rootElement.QualifiedName;
                    }
                    else
                    {
                        Types[rootElement.QualifiedName] = type;
                    }
                }
                else
                {
                    var classModel = type as ClassModel;
                    if (classModel != null)
                    {
                        classModel.Documentation.AddRange(GetDocumentation(rootElement));
                    }

                    type.RootElementName = rootElement.QualifiedName;
                }
            }

            foreach (var globalType in Set.GlobalTypes.Values.Cast<XmlSchemaType>())
            {
                var schema = globalType.GetSchema();
                var source = (schema == null ? null : new Uri(schema.SourceUri));
                var type = CreateTypeModel(source, globalType, globalType.QualifiedName);
            }
        }