package daruma.storage_manager.schema;

import daruma.util.LogWriter;

import daruma.storage_manager.schema.AnnotationElementChecker;

import daruma.storage_manager.StorageManager;
import daruma.storage_manager.StorageException;

import daruma.storage_manager.type_definition.TypeName;
import daruma.storage_manager.type_definition.TypeDefinition;
import daruma.storage_manager.type_definition.TypeException;
import daruma.storage_manager.type_definition.ElementName;
import daruma.storage_manager.type_definition.XMLSchemaElementDefinition;
import daruma.storage_manager.type_definition.AttributeDefinition;

import daruma.storage_manager.type_definition.types.SequenceTypeDefinition;
import daruma.storage_manager.type_definition.types.AllTypeDefinition;
import daruma.storage_manager.type_definition.types.ChoiceTypeDefinition;
import daruma.storage_manager.type_definition.types.MultipleOccursElementTypeDefinition;
import daruma.storage_manager.type_definition.types.ComplexTypeDefinition;
import daruma.storage_manager.type_definition.types.StringTypeDefinition;

import daruma.xml.UniversalName;
import daruma.xml.QName;
import daruma.xml.URI;
import daruma.xml.util.PrefixMap;
import daruma.xml.util.ElementUtil;

import daruma.util.Pair;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.util.Map;
import java.util.List;
import java.util.ArrayList;


public class XMLSchemaTypeParser
{
	private	XMLSchemaTypeParser()
	{
	}

	public	static	TypeDefinition	parse( Element  e ,
					       String  targetNamespace ,
					       PrefixMap  prefixMap ,
					       StorageManager  storage )
				throws XMLSchemaParserException
	{
		if ( e.getNamespaceURI() == null
		  || ! e.getNamespaceURI().equals( URI.XML_SCHEMA ) )
		{
			throw new XMLSchemaParserException
				  ( "element \"" + e.getLocalName()
				    + "\" isn't a member of "
				    + URI.XML_SCHEMA );
		}


		if ( e.getLocalName().equals( "simpleType" ) )
		{
			return( parseSimpleTypeOrContent
				( e , targetNamespace ,
				  prefixMap , storage ) );
		}
		else if ( e.getLocalName().equals( "complexType" ) )
		{
			return( parseComplexTypeOrComplexContentExtension
				( e , true , targetNamespace ,
				  prefixMap , storage ) );
		}
		else
		{
			throw new XMLSchemaParserException
				  ( "unexpected element \""
				    + e.getLocalName() + "\"" );
		}
	}


	public	static	TypeDefinition	parseSimpleTypeOrContent
					( Element  simpleTypeElement ,
					  String  targetNamespace ,
					  PrefixMap  prefixMap ,
					  StorageManager  storage )
						throws XMLSchemaParserException
	{
		TypeDefinition	ret = null;

		List<Element>	childElements = ElementUtil.getChildElements
							( simpleTypeElement );

		Element	child = null;

		for ( Element  e  :  childElements )
		{
			if ( e.getNamespaceURI() == null
			  || ! e.getNamespaceURI().equals( URI.XML_SCHEMA ) )
			{
				throw new XMLSchemaParserException
					( "element \""
					  + e.getLocalName()
					  + "\" isn't a member of "
					  + URI.XML_SCHEMA );
			}
			else if ( e.getLocalName().equals( "restriction" ) )
			{
				if ( child != null )
				{
					throw new XMLSchemaParserException
					  ( "too many child elements"
					    + " found in \""
					    + simpleTypeElement.getLocalName()
					    + "\"" );
				}

				child = e;
			}
			else if ( e.getLocalName().equals( "annotation" ) )
			{
				XMLSchemaTypeParser.checkAnnotation( e );
			}
			else
			{
				throw new XMLSchemaParserException
					( e.getLocalName()
					  + " in namespace "
					  + e.getNamespaceURI()
					  + " is not a valid child of "
					  + simpleTypeElement.getLocalName() );
			}
		}

		if ( child == null )
		{
			throw new XMLSchemaParserException
				( "unexpected empty child of \""
				  + simpleTypeElement.getLocalName() );
		}

		if ( ! child.hasAttribute( "base" ) )
		{
			throw new XMLSchemaParserException
				  ( child.getLocalName()
				    + " in namespace "
				    + child.getNamespaceURI()
				    + " must have \"base\" attribute" );
		}

		String	baseTypeQName = child.getAttribute( "base" );

		Pair<TypeName, TypeDefinition>	type;

		try
		{
			type = XMLSchemaTypeParser
				.getTypeDefinitionFromQNameString
				  ( baseTypeQName , prefixMap , storage );
		}
		catch( XMLSchemaParserException  e )
		{
			throw e;
		}

		TypeDefinition	typeDefinition = type.getSecond();

		if ( typeDefinition.isXMLComplexType() )
		{
			throw new XMLSchemaParserException
				  ( "base type " + baseTypeQName
				    + " is not a simpleType" );
		}

		return( XMLSchemaTypeParser
			.appendSimpleTypeRestrictionToTypeDefinition
			( typeDefinition , child ) );
	}


	public	static	TypeDefinition
			    appendSimpleTypeRestrictionToTypeDefinition
						( TypeDefinition  type ,
						  Element  restrictonElement )
						throws XMLSchemaParserException
	{
		// XXX: loosely parsing

		TypeDefinition	result = type;

		for ( Element  r
			:  ElementUtil.getChildElements( restrictonElement ) )
		{
			if ( r.getNamespaceURI() == null
			  || ! r.getNamespaceURI().equals( URI.XML_SCHEMA ) )
			{
				throw new XMLSchemaParserException
				  ( r.getLocalName()
				    + " in namespace "
				    + r.getNamespaceURI()
				    + " is not a schema element" );
			}

			if ( r.getLocalName().equals( "maxLength" )
			  || r.getLocalName().equals( "minLength" ) )
			{
				if ( ! r.hasAttribute( "value" ) )
				{
					throw new XMLSchemaParserException
					( r.getLocalName()
					  + " in namespace "
					  + r.getNamespaceURI()
					  + " does not have attribute"
					  + " \"value\"" );
				}

				String	value = r.getAttribute( "value" );

				long	limit;

				try
				{
					limit = Long.decode( value );
				}
				catch( NumberFormatException  e )
				{
					throw new XMLSchemaParserException
					( "\"" + value + "\""
					  + " is not a valid integer" );
				}

				if ( limit < 0 )
				{
					throw new XMLSchemaParserException
					( "\"" + value + "\""
					  + " is not a unsigned integer" );
				}

				// XXX
				if ( type instanceof StringTypeDefinition )
				{
					result = ((StringTypeDefinition)result)
								.deepCopy();

					if ( r.getLocalName().equals
					     ( "minLength" ) )
					{
						((StringTypeDefinition)result)
							.setMinLength( limit );
					}
					else if ( r.getLocalName().equals
						  ( "maxLength" ) )
					{
						((StringTypeDefinition)result)
							.setMaxLength( limit );
					}
				}
			}
			else if ( r.getLocalName().equals( "enumeration" ) )
			{
				if ( ! r.hasAttribute( "value" ) )
				{
					throw new XMLSchemaParserException
					( r.getLocalName()
					  + " in namespace "
					  + r.getNamespaceURI()
					  + " does not have attribute"
					  + " \"value\"" );
				}

				String	value = r.getAttribute( "value" );

				// XXX
				if ( type instanceof StringTypeDefinition )
				{
					result = ((StringTypeDefinition)result)
								.deepCopy();

					((StringTypeDefinition)result)
						.addEnumeration( value );
				}
			}
			else if ( r.getLocalName().equals( "annotation" ) )
			{
				XMLSchemaTypeParser.checkAnnotation( r );
			}
			else
			{
				// XXX: unsupported yet
			}
		}

		return( result );
	}


	public	static	TypeDefinition
			  parseComplexTypeOrComplexContentExtension
					( Element  complexTypeElement ,
					  boolean  allowExtraAttributes ,
					  String  targetNamespace ,
					  PrefixMap  prefixMap ,
					  StorageManager  storage )
						throws XMLSchemaParserException
	{
		TypeDefinition	ret = null;

		NodeList	childNodes
				  = complexTypeElement.getChildNodes();

		int	i = 0;

		for ( ;  i < childNodes.getLength()  ;  ++ i )
		{
			Node	n = childNodes.item(i);

			if ( ! (n instanceof Element) )
			{
				continue;
			}

			Element	e = (Element)n;

			if ( e.getLocalName().equals( "sequence" )
			  && e.getNamespaceURI() != null
			  && e.getNamespaceURI().equals(URI.XML_SCHEMA) )
			{
				ret = XMLSchemaTypeParser
				      .parseSequence( e , targetNamespace ,
						      prefixMap , storage ,
						      true );
			}
			else if ( e.getLocalName().equals( "all" )
			       && e.getNamespaceURI() != null
			       && e.getNamespaceURI().equals(URI.XML_SCHEMA) )
			{
				ret = XMLSchemaTypeParser
				      .parseAll( e , targetNamespace ,
						 prefixMap , storage ,
						 true );
			}
			else if ( e.getLocalName().equals( "simpleContent" )
			       && e.getNamespaceURI() != null
			       && e.getNamespaceURI().equals(URI.XML_SCHEMA) )
			{
				ret = XMLSchemaTypeParser
					.parseSimpleTypeOrContent
					 ( e , targetNamespace ,
					   prefixMap , storage );
			}
			else if ( e.getLocalName().equals( "complexContent" )
			       && e.getNamespaceURI() != null
			       && e.getNamespaceURI().equals(URI.XML_SCHEMA) )
			{
				ret = XMLSchemaTypeParser
					.parseComplexContent
					 ( e , targetNamespace ,
					   prefixMap , storage );
			}
			else if ( e.getLocalName().equals( "choice" )
			       && e.getNamespaceURI() != null
			       && e.getNamespaceURI().equals(URI.XML_SCHEMA) )
			{
				ret = XMLSchemaTypeParser
					.parseChoice
					 ( e , targetNamespace ,
					   prefixMap , storage ,
					   false );
			}
			else if ( e.getLocalName().equals( "annotation" )
			       && e.getNamespaceURI() != null
			       && e.getNamespaceURI().equals(URI.XML_SCHEMA) )
			{
				// check only
				XMLSchemaTypeParser.checkAnnotation( e );

				continue;
			}
			else
			{
				throw new XMLSchemaParserException
					  ( "unexpected element \""
					    + e.getLocalName()
					    + "\" in namespace "
					    + e.getNamespaceURI() );
			}

			i++;
			break;
		}

		if ( ret == null )
		{
			throw new XMLSchemaParserException
				( "too few child elements in \""
				  + complexTypeElement.getLocalName()
				  + "\"" );
		}

		if ( allowExtraAttributes )
		{
			List<AttributeDefinition>
			  attributeDefs = new ArrayList<AttributeDefinition>();

			for ( ;  i < childNodes.getLength()  ;  ++ i )
			{
				Node	n = childNodes.item(i);

				if ( ! (n instanceof Element) )
				{
					continue;
				}

				Element	e = (Element)n;

				if ( e.getLocalName().equals( "attribute" )
				  && e.getNamespaceURI() != null
				  && e.getNamespaceURI()
					     .equals(URI.XML_SCHEMA) )
				{
					AttributeDefinition
					  def = XMLSchemaTypeParser
						.parseAttribute
						 ( e ,
						   targetNamespace ,
						   prefixMap ,
						   storage );

					attributeDefs.add( def );
				}
				else if ( e.getLocalName()
						.equals( "annotation" )
				       && e.getNamespaceURI() != null
				       && e.getNamespaceURI()
						.equals(URI.XML_SCHEMA) )
				{
					// check only
					XMLSchemaTypeParser
						.checkAnnotation( e );
				}
				else
				{
					throw new XMLSchemaParserException
						( "unexpected element \""
						  + e.getLocalName()
						  + "\" in namespace "
						  + e.getNamespaceURI() );
				}
			}

			ComplexTypeDefinition
				c = new ComplexTypeDefinition( ret );

			for( AttributeDefinition  a  :  attributeDefs )
			{
				try
				{
					c.addAttributeDefinition( a );
				}
				catch( TypeException  e )
				{
					throw new XMLSchemaParserException
							( e.getMessage() , e );
				}
			}

			ret = c;
		}

		return( ret );
	}


	public	static	TypeDefinition	parseComplexContent
					( Element  complexContentElement ,
					  String  targetNamespace ,
					  PrefixMap  prefixMap ,
					  StorageManager  storage )
						throws XMLSchemaParserException
	{
		List<Element>	childElements = ElementUtil.getChildElements
						     ( complexContentElement );

		Element	child = null;

		for ( Element  e  :  childElements )
		{
			if ( e.getNamespaceURI() == null
			  || ! e.getNamespaceURI().equals( URI.XML_SCHEMA ) )
			{
				throw new XMLSchemaParserException
					( "element \""
					  + e.getLocalName()
					  + "\" isn't a member of "
					  + URI.XML_SCHEMA );
			}
			else if ( e.getLocalName().equals( "annotation" ) )
			{
				XMLSchemaTypeParser.checkAnnotation( e );
			}
			else
			{
				if ( child != null )
				{
					throw new XMLSchemaParserException
					  ( "too many child elements"
					    + " found in \""
					    + complexContentElement
						    .getLocalName() + "\"" );
				}

				child = e;
			}
		}

		if ( child == null )
		{
			throw new XMLSchemaParserException
				( "unexpected empty child of \""
				  + complexContentElement.getLocalName() );
		}


		if ( child.getLocalName().equals( "extension" ) )
		{
		}
		else if ( child.getLocalName().equals( "restriction" ) )
		{
			// XXX: not implemented yet
			throw new XMLSchemaParserException
			  ( child.getLocalName()
			    + " in namespace "
			    + child.getNamespaceURI()
			    + " in complexType has not been implemented yet" );
		}
		else
		{
			throw new XMLSchemaParserException
				  ( child.getLocalName()
				    + " in namespace "
				    + child.getNamespaceURI()
				    + " is not a valid child of "
				    + complexContentElement.getLocalName() );
		}


		//
		// child is `extension`
		//
		if ( ! child.hasAttribute( "base" ) )
		{
			throw new XMLSchemaParserException
				  ( child.getLocalName()
				    + " in namespace "
				    + child.getNamespaceURI()
				    + " must have \"base\" attribute" );
		}

		String	baseTypeQName = child.getAttribute( "base" );

		Pair<TypeName, TypeDefinition>	type;

		try
		{
			type = XMLSchemaTypeParser
				.getTypeDefinitionFromQNameString
				  ( baseTypeQName , prefixMap , storage );
		}
		catch( XMLSchemaParserException  e )
		{
			throw e;
		}

		TypeDefinition	baseTypeDefinition = type.getSecond();

		if ( ! baseTypeDefinition.isXMLComplexType() )
		{
			throw new XMLSchemaParserException
				  ( "base type" + baseTypeQName
				    + " is not a complexType" );
		}


		TypeDefinition	additionalDefinition
				= XMLSchemaTypeParser
				  .parseComplexTypeOrComplexContentExtension
							    ( child ,
							      true ,
							      targetNamespace ,
							      prefixMap ,
							      storage );

		SequenceTypeDefinition	seq = new SequenceTypeDefinition();

		seq.addChild( baseTypeDefinition );
		seq.addChild( additionalDefinition );

		return( seq );
	}


	public	static	SequenceTypeDefinition
				parseSequence( Element  sequenceElement ,
					       String  targetNamespace ,
					       PrefixMap  prefixMap ,
					       StorageManager  storage ,
					       boolean  isTopLevel )
						throws XMLSchemaParserException
	{
		SequenceTypeDefinition	seq = new SequenceTypeDefinition();

		NodeList	childNodes = sequenceElement.getChildNodes();

		for ( int  i = 0  ;  i < childNodes.getLength()  ;  ++ i )
		{
			Node	n = childNodes.item(i);

			if ( ! (n instanceof Element) )
			{
				continue;
			}

			Element	e = (Element)n;

			if ( e.getNamespaceURI() == null
			  || ! e.getNamespaceURI().equals( URI.XML_SCHEMA ) )
			{
				throw new XMLSchemaParserException
					  ( "invalid tag: " + e.getLocalName()
					    + " in namespace "
					    + e.getNamespaceURI()
					    + ": expected namespace was "
					    + URI.XML_SCHEMA );
			}

			if ( e.getLocalName().equals( "element" ) )
			{
				XMLSchemaElementDefinition
				    def = XMLSchemaTypeParser
					  .parseElement( e ,
							 targetNamespace ,
							 prefixMap ,
							 storage );

				if ( def.maxOccursIsGraterEquals( 2 ) )
				{
					seq.addChild
					( new MultipleOccursElementTypeDefinition
					  ( def.getTypeName() ,
					    def.getElementName() ,
					    def.getMinOccurs() ,
					    def.getMaxOccurs() ) );
				}
				else
				{
					seq.addChild( def );
				}
			}
			else if ( e.getLocalName().equals( "sequence" ) )
			{
				seq.addChild( parseSequence
					      ( e ,
						targetNamespace ,
						prefixMap ,
						storage ,
						false ) );
			}
			else if ( e.getLocalName().equals( "choice" ) )
			{
				seq.addChild( parseChoice
					      ( e ,
						targetNamespace ,
						prefixMap ,
						storage ,
						false ) );
			}
			else if ( e.getLocalName().equals( "annotation" ) )
			{
				// check only
				XMLSchemaTypeParser.checkAnnotation( e );
			}
			else
			{
				throw new XMLSchemaParserException
					  ( "unexpected type: "
					    + e.getLocalName()
					    + " in namespace "
					    + e.getNamespaceURI() );
			}
		}

		return( seq );
	}


	public	static	AllTypeDefinition
				parseAll( Element  allElement ,
					  String  targetNamespace ,
					  PrefixMap  prefixMap ,
					  StorageManager  storage ,
					  boolean  isTopLevel )
						throws XMLSchemaParserException
	{
		AllTypeDefinition	all = new AllTypeDefinition();

		ElementName	allElementName = new ElementName( allElement );

		NodeList	childNodes = allElement.getChildNodes();

		for ( int  i = 0  ;  i < childNodes.getLength()  ;  ++ i )
		{
			Node	n = childNodes.item(i);

			if ( ! (n instanceof Element) )
			{
				continue;
			}

			Element	e = (Element)n;

			if ( e.getNamespaceURI() == null
			  || ! e.getNamespaceURI().equals( URI.XML_SCHEMA ) )
			{
				throw new XMLSchemaParserException
					  ( "invalid tag: " + e.getLocalName()
					    + " in namespace "
					    + e.getNamespaceURI()
					    + ": expected namespace was "
					    + URI.XML_SCHEMA );
			}

			if ( e.getLocalName().equals( "element" ) )
			{
				XMLSchemaElementDefinition
				    def = XMLSchemaTypeParser
					  .parseElement( e ,
							 targetNamespace ,
							 prefixMap ,
							 storage );

				if ( def.maxOccursIsGraterEquals( 2 ) )
				{
					throw new XMLSchemaParserException
					  ( "element in `all'"
					    + "should be single element" );
				}
				else
				{
					all.addChild( def );
				}
			}
			else if ( e.getLocalName().equals( "annotation" ) )
			{
				// check only
				XMLSchemaTypeParser.checkAnnotation( e );
			}
			else if ( e.getLocalName().equals( "sequence" ) )
			{
				throw new XMLSchemaParserException
					  ( "unexpected element \""
					    + e.getLocalName()
					    + "\" in namespace "
					    + e.getNamespaceURI()
					    + " as a child of "
					    + allElementName.toString() );
			}
			else
			{
				throw new XMLSchemaParserException
					  ( "unexpected type: "
					    + e.getLocalName()
					    + " in namespace "
					    + e.getNamespaceURI()
					    + ", expected "
					    + "element or annotation" );
			}
		}

		return( all );
	}


	public	static	ChoiceTypeDefinition
				parseChoice( Element  choiceElement ,
					     String  targetNamespace ,
					     PrefixMap  prefixMap ,
					     StorageManager  storage ,
					     boolean  isTopLevel )
						throws XMLSchemaParserException
	{
		ChoiceTypeDefinition	choice = new ChoiceTypeDefinition();

		NodeList	childNodes = choiceElement.getChildNodes();

		for ( int  i = 0  ;  i < childNodes.getLength()  ;  ++ i )
		{
			Node	n = childNodes.item(i);

			if ( ! (n instanceof Element) )
			{
				continue;
			}

			Element	e = (Element)n;

			if ( e.getNamespaceURI() == null
			  || ! e.getNamespaceURI().equals( URI.XML_SCHEMA ) )
			{
				throw new XMLSchemaParserException
					  ( "invalid tag: " + e.getLocalName()
					    + " in namespace "
					    + e.getNamespaceURI()
					    + ": expected namespace was "
					    + URI.XML_SCHEMA );
			}

			if ( e.getLocalName().equals( "element" ) )
			{
				XMLSchemaElementDefinition
				    def = XMLSchemaTypeParser
					  .parseElement( e ,
							 targetNamespace ,
							 prefixMap ,
							 storage );

				// XXX: not supported multiple occurs element
				// XXX: check {min,max}Occures
				choice.addChild( def );
			}
			else if ( e.getLocalName().equals( "sequence" ) )
			{
				choice.addChild( XMLSchemaTypeParser
						 .parseSequence
						   ( e , targetNamespace ,
						     prefixMap , storage ,
						     false ) );
			}
			else if ( e.getLocalName().equals( "choice" ) )
			{
				choice.addChild( XMLSchemaTypeParser
						 .parseChoice
						   ( e , targetNamespace ,
						     prefixMap , storage ,
						     false ) );
			}
			else if ( e.getLocalName().equals( "group" )
			       || e.getLocalName().equals( "any" ) )
			{
				// XXX: not implemented yet
				throw new XMLSchemaParserException
				  ( new ElementName( choiceElement ).toString()
				    + " in " + choiceElement
				    + " has not been implemented yet" );
			}
			else if ( e.getLocalName().equals( "annotation" ) )
			{
				//
				// XXX: should check this element is
				//      first child or not

				// check only
				XMLSchemaTypeParser.checkAnnotation( e );
			}
			else
			{
				throw new XMLSchemaParserException
					  ( "unexpected type: "
					    + e.getLocalName()
					    + " in namespace "
					    + e.getNamespaceURI()
					    + ", expected "
					    + "element or annotation" );
			}
		}

		return( choice );
	}


	private	static	XMLSchemaElementDefinition
					parseElement( Element  e ,
						      String  targetNamespace ,
						      PrefixMap  prefixMap ,
						      StorageManager  storage )
						throws XMLSchemaParserException
	{
		if ( ! e.hasAttribute( "name" ) )
		{
			throw new XMLSchemaParserException
				  ( e.getLocalName()
				    + " in namespace "
				    + e.getNamespaceURI()
				    + " must have"
				    + " \"name\" attribute" );
		}

		if ( ! e.hasAttribute( "type" ) )
		{
			throw new XMLSchemaParserException
				  ( "\"type\" attribute not found,"
				    + " in " + e.getLocalName()
				    + " in namespace "
				    + e.getNamespaceURI()
				    + ", sorry, simpleType or simpleContent"
				    + " not supported yet" );
		}


		String	name = e.getAttribute( "name" );
		String	typeQNameString = e.getAttribute( "type" );
		QName	typeQName = new QName( typeQNameString );

		if ( ! typeQName.isValid() )
		{
			throw new XMLSchemaParserException
				  ( "type \"" + e.getAttribute( "type" )
				    + "\" is not valid QName" );
		}


		String	typeNamespace = prefixMap.get( typeQName.getPrefix() );

		if ( typeNamespace == null )
		{
			throw new XMLSchemaParserException
				  ( "prefix of \"" + typeQNameString
				    + "\" was not found" );
		}


		LogWriter.qwrite( "DEBUG",
				  "element name = [", name, "]" );
		LogWriter.qwrite( "DEBUG",
				  "element type = [{", typeNamespace, "}",
				  typeQName.getLocalName(), "]" );

		TypeName  typeName = new TypeName( typeNamespace ,
						   typeQName.getLocalName() );

		TypeDefinition	type;
		try
		{
			type = storage.getTypeDefinition( typeName );
		}
		catch( StorageException  se )
		{
			throw new XMLSchemaParserException( se );
		}


		if ( type == null )
		{
			throw new XMLSchemaParserException
				  ( "type \"" + typeName.getLocalName()
				    + "\" in namespace \""
				    + typeName.getNamespace()
				    + "\" not found" );
		}

		LogWriter.qwrite( "DEBUG",
				  "type = [", typeName.getLocalName(),
				  "], namespace = [", typeName.getNamespace(),
				  "]" );

		ElementName	elementName = new ElementName
						  ( targetNamespace ,
						    name );

		try
		{
			storage.registerUniversalNameIfNotRegistrated
							       ( elementName );
		}
		catch( StorageException  se )
		{
			throw new XMLSchemaParserException
				  ( "can't register " + elementName , se );
		}


		long	minOccurs = 1;
		long	maxOccurs = 1;

		if ( e.hasAttribute( "maxOccurs" ) )
		{
			String	attrValue = e.getAttribute( "maxOccurs" );

			if ( attrValue.equals( "unbounded") )
			{
				maxOccurs = XMLSchemaElementDefinition
							.MAX_OCCURS_UNBOUNDED;
			}
			else
			{
				try
				{
					maxOccurs = Integer.parseInt
							  ( e.getAttribute
							    ( "maxOccurs" ) );
				}
				catch( NumberFormatException ne )
				{
					throw new XMLSchemaParserException
						( "value of maxOccurs"
						  + " not a valid integer" );
				}
			}
		}

		if ( e.hasAttribute( "minOccurs" ) )
		{
			try
			{
				minOccurs = Integer.parseInt
					    ( e.getAttribute( "minOccurs" ) );
			}
			catch( NumberFormatException ne )
			{
				throw new XMLSchemaParserException
					 ( "value of minOccurs"
					   + " not a valid integer" );
			}
		}


		if ( minOccurs < 0 )
		{
			throw new XMLSchemaParserException
				  ( "negative value of minOccurs" );
		}
		else if ( maxOccurs != XMLSchemaElementDefinition
						  .MAX_OCCURS_UNBOUNDED
		       && maxOccurs < 0 )
		{
			throw new XMLSchemaParserException
				  ( "negative value of maxOccurs" );
		}
		else if ( maxOccurs != XMLSchemaElementDefinition
						  .MAX_OCCURS_UNBOUNDED
		       && minOccurs > maxOccurs )
		{
			throw new XMLSchemaParserException
				  ( "minOccurs should be"
				    + " less or equals to maxOccurs" );
		}

		return( new XMLSchemaElementDefinition
			( type , elementName , typeName ,
			  minOccurs , maxOccurs ) );
	}


	private	static	AttributeDefinition
				parseAttribute( Element  e ,
						String  targetNamespace ,
						PrefixMap  prefixMap ,
						StorageManager  storage )
					throws XMLSchemaParserException
	{

		//
		// form
		//
		// XXX: assume schema attributeFormDefault is not specified
		String	attrNamespace = null;

		if ( e.hasAttributeNS( null , "form" ) )
		{
			String	form = e.getAttributeNS( null , "form" );

			if ( form.equals( "qualified" ) )
			{
				attrNamespace = targetNamespace;
			}
			else if ( form.equals( "unqualified" ) )
			{
				attrNamespace = null;
			}
			else
			{
				throw new XMLSchemaParserException
					( "invalid \"form\" attribute ["
					  + form + "] of element ["
					  + new UniversalName( e ) );
			}
		}


		//
		// name
		//
		if ( ! e.hasAttributeNS( null , "name" ) )
		{
			throw new XMLSchemaParserException
				  ( e.getLocalName()
				    + " in namespace "
				    + e.getNamespaceURI()
				    + " must have \"name\" attribute" );
		}

		String	name = e.getAttributeNS( null , "name" );

		UniversalName	attributeName = new UniversalName
						    ( attrNamespace , name );

		try
		{
			storage.registerUniversalNameIfNotRegistrated
				( attributeName );
		}
		catch( StorageException  se )
		{
			throw new XMLSchemaParserException
				  ( "can't register " + attributeName , se );
		}


		//
		// type
		//
		if ( ! e.hasAttributeNS( null , "type" ) )
		{
			throw new XMLSchemaParserException
				  ( e.getLocalName()
				    + " in namespace "
				    + e.getNamespaceURI()
				    + " must have \"type\" attribute" );
		}

		String	typeQName = e.getAttributeNS( null , "type" );

		Pair<TypeName, TypeDefinition>
			t = XMLSchemaTypeParser
			    .getTypeDefinitionFromQNameString( typeQName ,
							       prefixMap ,
							       storage );

		if ( t.getSecond().isXMLComplexType() )
		{
			throw new XMLSchemaParserException
				  ( "attribute type ["
				    + typeQName + "]"
				    + " must be a simpleType" );
		}


		//
		// use
		//
		AttributeDefinition.UseType
			useType = AttributeDefinition.UseType.OPTIONAL;

		if ( e.hasAttributeNS( null , "use" ) )
		{
			String	useTypeString
				= e.getAttributeNS( null , "use" );

			if ( useTypeString.equals( "optional" ) )
			{
				useType = AttributeDefinition.UseType.OPTIONAL;
			}
			else if ( useTypeString.equals( "required" ) )
			{
				useType = AttributeDefinition.UseType.REQUIRED;
			}
			else if ( useTypeString.equals( "prohibit" ) )
			{
				useType = AttributeDefinition.UseType.PROHIBIT;
			}
			else
			{
				throw new XMLSchemaParserException
					( "invalid \"use\" attribute ["
					  + useTypeString + "] of element ["
					  + new UniversalName( e ) );
			}
		}


		AttributeDefinition	ret = new AttributeDefinition
						  ( t.getSecond() ,
						    attributeName ,
						    t.getFirst() ,
						    useType );

		return( ret );
	}


	//
	// utilities
	//
	static	Pair<TypeName, TypeDefinition>
		getTypeDefinitionFromQNameString( String  typeQNameString ,
						  PrefixMap  prefixMap ,
						  StorageManager  storage )
						throws XMLSchemaParserException
	{
		QName	typeQName = new QName( typeQNameString );

		if ( ! typeQName.isValid() )
		{
			throw new XMLSchemaParserException
			  ( "type \"" + typeQNameString
			    + "\" is not a valid QName" );
		}

		String	typeNamespace = prefixMap.get( typeQName.getPrefix() );

		if ( typeNamespace == null )
		{
			throw new XMLSchemaParserException
				  ( "prefix \"" + typeQName.getPrefix()
				    + "\" was not found" );
		}

		// XXX
		LogWriter.qwrite("DEBUG",
				 "element type = [{", typeNamespace, "}",
				 typeQName.getLocalName(), "]" );

		TypeName  typeName = new TypeName( typeNamespace ,
						   typeQName.getLocalName() );

		TypeDefinition	type;
		try
		{
			type = storage.getTypeDefinition( typeName );
		}
		catch( StorageException  se )
		{
			throw new XMLSchemaParserException( se );
		}


		if ( type == null )
		{
			throw new XMLSchemaParserException
				( "type \"" + typeName.getLocalName()
				  + "\" in namespace \""
				  + typeName.getNamespace() + "\" not found" );
		}

		LogWriter.qwrite("DEBUG",  "type = [", typeName.getLocalName(),
				 "], namespace = [", typeName.getNamespace(),
				 "]" );

		return( new Pair<TypeName, TypeDefinition>
			( typeName , type ) );
	}


	public	static	void	checkAnnotation( Element  element )
					throws XMLSchemaParserException
	{
		AnnotationElementChecker.validateAnnotation( element );
	}
}
