package daruma.storage_manager.schema;

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

import javax.xml.transform.TransformerException;

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

import daruma.storage_manager.type_definition.ElementName;
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.ElementInfo;
import daruma.xml.URI;
import daruma.xml.QName;
import daruma.xml.util.PrefixMap;
import daruma.xml.util.ElementUtil;
import daruma.xml.util.XMLFormatConverter;

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

import daruma.util.LogWriter;

public class XMLSchemaParseAndRegister
{
	private	String	targetNamespace;
	private	String	schemaID;

	public	XMLSchemaParseAndRegister()
	{
	}

	public	String	getTargetNamespace()
	{
		return( this.targetNamespace );
	}

	public	String	getSchemaID()
	{
		return( this.schemaID );
	}

	public	void	parseXMLSchemaString( String  schemaString ,
					      long  internalSchemaID ,
					      StorageManager  storage ,
					      boolean  cacheUpdateOnly )
					 throws XMLSchemaParserException ,
						StorageException
	{
		Element		schemaElement = null;

		try
		{
			schemaElement = XMLFormatConverter
					.stringToDOMElement( schemaString );
		}
		catch( TransformerException  e )
		{
			throw new XMLSchemaParserException( e );
		}


		PrefixMap	prefixMap = new PrefixMap();

		ElementUtil.addPrefixMappingToPrefixMap( prefixMap ,
							 schemaElement );

		this.parseXMLSchemaDOMElement( schemaElement ,
					       internalSchemaID ,
					       prefixMap ,
					       storage ,
					       cacheUpdateOnly );
	}


	public	void	parseXMLSchemaDOMElement
					( Element  schemaElement ,
					  long  internalSchemaID ,
					  PrefixMap  prefixMap ,
					  StorageManager  storage ,
					  boolean  cacheUpdateOnly )
				 throws XMLSchemaParserException ,
					StorageException
	{
		LogWriter.qwrite("DEBUG",  "========================");


		//
		// get targetNamespace
		//
		if ( ! schemaElement.hasAttribute( "targetNamespace" ) )
		{
			throw new XMLSchemaParserException
				   ( "no targetNamespace in schema" );
		}

		this.targetNamespace = schemaElement.getAttribute
							( "targetNamespace" );



		if ( ! schemaElement.hasAttributeNS( URI.MISP , "id" ) )
		{
			throw new XMLSchemaParserException
				   ( "\"id\" attribute in namespace "
				     + URI.MISP
				     + " not found" );
		}

		this.schemaID = schemaElement.getAttributeNS
						 ( URI.MISP , "id" );
		if ( schemaID.length() == 0 )
		{
			throw new XMLSchemaParserException
				   ( "empty \"id\" attribute in namespace "
				     + URI.MISP + " is not a valid id" );
		}



		//
		// prepare top level type & element
		//
		List<Element>	topLevelTypeList = new ArrayList<Element>();
		List<Element>	topLevelElementList = new ArrayList<Element>();

		for ( Element  e
			:  ElementUtil.getChildElements( schemaElement ) )
		{
			if ( e.getNamespaceURI() == null
			  || ! e.getNamespaceURI().equals( URI.XML_SCHEMA ) )
			{
				throw new XMLSchemaParserException
					   ( "invalid XMLSchema element \""
					     + e.getLocalName() + "\""
					     + ", not in namespace "
					     + URI.XML_SCHEMA );
			}

			if ( e.getLocalName().equals( "simpleType" )
			  || e.getLocalName().equals( "complexType" ) )
			{
				topLevelTypeList.add( e );
			}
			else if ( e.getLocalName().equals( "element" ) )
			{
				topLevelElementList.add( e );
			}
			else if ( e.getLocalName().equals( "import" ) )
			{
				// no operation
			}
			else if ( e.getLocalName().equals( "annotation" ) )
			{
				XMLSchemaTypeParser.checkAnnotation( e );
			}
			else if ( e.getLocalName().equals( "schema" ) )
			{
				throw new XMLSchemaParserException
					( "unexpected \"schema\" element" );
			}
			else
			{
				throw new XMLSchemaParserException
					( "unexpected element ["
					  + e.getLocalName() + "]" );
			}
		}


		//
		// top level type
		//
		// XXX: should reorder schemas,
		//      check schema reference graph
		//
		/*
		for ( Element  e : topLevelTypeList )
		*/
		for ( int  i = topLevelTypeList.size() - 1  ;
		      i >= 0  ;  i -- )
		{
			Element	e = topLevelTypeList.get(i);

			if ( ! e.hasAttribute( "name" ) )
			{
				throw new XMLSchemaParserException
				   ( "\"" + e.getLocalName() + "\""
				     + " tag must have \"name\" attribute" );
			}

			String	name = e.getAttribute( "name" );

			TypeDefinition	typeDefinition;
			try
			{
				typeDefinition
				    = XMLSchemaTypeParser
					.parse( e , this.targetNamespace ,
						prefixMap , storage );
			}
			catch( XMLSchemaParserException  pe )
			{
				throw pe;
			}

			storage.registerTypeDefinition
				( new TypeName( this.targetNamespace , name ) ,
				  typeDefinition ,
				  schemaID ,
				  internalSchemaID ,
				  cacheUpdateOnly );
		}


		//
		// top level element
		//
		for ( Element  e : topLevelElementList )
		{
			if ( ! e.hasAttribute( "name" ) )
			{
				throw new XMLSchemaParserException
					   ( "\"element\" tag must have"
					     + " \"name\" attribute" );
			}
			String	name = e.getAttribute( "name" );


			if ( ! e.hasAttribute( "type" ) )
			{
				// XXX: tentative specification
				throw new XMLSchemaParserException
					   ( "\"element\" tag must have"
					     + " \"type\" attribute" );
			}


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

			if ( ! typeQName.isValid() )
			{
				throw new XMLSchemaParserException
					( "invalid QName ["
					  + typeQName.toString() + "]" );
			}

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

			if ( typeNamespace == null )
			{
				throw new XMLSchemaParserException
					( "unbounded prefix ["
					  + (typeQName.getPrefix() != null ?
					     typeQName.getPrefix() : "")
					  + "] " );
			}


			try
			{
				ElementInfo elementInfo = new ElementInfo();
				TypeName typeName
					   = new TypeName
						 ( typeNamespace,
						   typeQName.getLocalName() );

				TypeDefinition type
				    = storage.getTypeDefinition( typeName );

				if ( type == null )
				{
					throw new XMLSchemaParserException
						  ( "type "
						    + typeName
						    + " not found" );
				}

				type.getCompositeSQLDataType( storage,
							      null,
							      elementInfo );

				storage.registerElementDefinition
				  ( new ElementName( this.targetNamespace ,
						     name ) ,
				    typeName,
				    elementInfo ,
				    internalSchemaID ,
				    cacheUpdateOnly );
			}
			catch( StorageException  se )
			{
				throw se;
			}
			catch( TypeException  te )
			{
				throw new StorageException( te );
			}
		}

		LogWriter.qwrite("DEBUG",  "========================");
	}
}
